infinity-ui-elements 1.8.12 → 1.8.14

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.esm.js CHANGED
@@ -61,6 +61,14 @@ const iconRegistry = {
61
61
  close: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
62
62
  <path d="M18 6L6 18M6 6L18 18" stroke="#081416" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
63
63
  </svg>`,
64
+ upload: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
65
+ <path d="M4 19H20V12H22V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V12H4V19ZM13 9V16H11V9H6L12 3L18 9H13Z" fill="#081416"/>
66
+ </svg>
67
+ `,
68
+ file: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
69
+ <path d="M19.1111 21H4.88889C4.39797 21 4 20.5971 4 20.1V3.9C4 3.40295 4.39797 3 4.88889 3H19.1111C19.602 3 20 3.40295 20 3.9V20.1C20 20.5971 19.602 21 19.1111 21ZM18.2222 19.2V4.8H5.77778V19.2H18.2222ZM8.44444 9.3H15.5556V11.1H8.44444V9.3ZM8.44444 12.9H15.5556V14.7H8.44444V12.9Z" fill="#081416"/>
70
+ </svg>
71
+ `,
64
72
  };
65
73
  const Icon = ({ name, size = 24, className = "", style = {}, ...props }) => {
66
74
  const svgContent = iconRegistry[name];
@@ -3421,6 +3429,208 @@ const Skeleton = React.forwardRef(({ className, containerClassName, containerSty
3421
3429
  });
3422
3430
  Skeleton.displayName = "Skeleton";
3423
3431
 
3432
+ const selectTriggerVariants = cva("flex items-center gap-1 transition-all font-display font-size-100 leading-100", {
3433
+ variants: {
3434
+ size: {
3435
+ small: "px-2 text-xs",
3436
+ medium: "px-2 text-sm",
3437
+ large: "px-3 text-base",
3438
+ },
3439
+ validationState: {
3440
+ none: "",
3441
+ positive: "",
3442
+ negative: "",
3443
+ },
3444
+ isDisabled: {
3445
+ true: "opacity-60 cursor-not-allowed",
3446
+ false: "cursor-pointer hover:opacity-80",
3447
+ },
3448
+ },
3449
+ defaultVariants: {
3450
+ size: "medium",
3451
+ validationState: "none",
3452
+ isDisabled: false,
3453
+ },
3454
+ });
3455
+ const SelectTextField = React.forwardRef(({ textValue: controlledTextValue, defaultTextValue, onTextChange, selectOptions = [], selectValue: controlledSelectValue, defaultSelectValue, onSelectChange, selectPlaceholder = "Select", selectTriggerClassName, selectMenuClassName, selectMenuWidth = "auto", selectSectionHeading, selectEmptyTitle = "No options available", selectEmptyDescription = "There are no options to select from.", selectEmptyIcon, label, helperText, errorText, successText, validationState = "none", isDisabled = false, isRequired = false, isOptional = false, size = "medium", containerClassName, labelClassName, inputClassName, className, ...textFieldProps }, ref) => {
3456
+ const [uncontrolledTextValue, setUncontrolledTextValue] = React.useState(defaultTextValue || "");
3457
+ const [uncontrolledSelectValue, setUncontrolledSelectValue] = React.useState(defaultSelectValue);
3458
+ const [isSelectOpen, setIsSelectOpen] = React.useState(false);
3459
+ const [dropdownPlacement, setDropdownPlacement] = React.useState("bottom");
3460
+ const selectRef = React.useRef(null);
3461
+ const dropdownContainerRef = React.useRef(null);
3462
+ const componentRef = React.useRef(null);
3463
+ const textValue = controlledTextValue !== undefined
3464
+ ? controlledTextValue
3465
+ : uncontrolledTextValue;
3466
+ const selectValue = controlledSelectValue !== undefined
3467
+ ? controlledSelectValue
3468
+ : uncontrolledSelectValue;
3469
+ // Find the selected option
3470
+ const selectedOption = selectOptions.find((opt) => opt.value === selectValue);
3471
+ // Determine which helper text to show
3472
+ const displayHelperText = errorText || successText || helperText;
3473
+ const currentValidationState = errorText
3474
+ ? "negative"
3475
+ : successText
3476
+ ? "positive"
3477
+ : validationState;
3478
+ const handleTextChange = (e) => {
3479
+ const newValue = e.target.value;
3480
+ if (onTextChange) {
3481
+ onTextChange(newValue);
3482
+ }
3483
+ else {
3484
+ setUncontrolledTextValue(newValue);
3485
+ }
3486
+ };
3487
+ const handleSelectOpenChange = (newOpen) => {
3488
+ if (!isDisabled) {
3489
+ setIsSelectOpen(newOpen);
3490
+ }
3491
+ };
3492
+ const toggleSelectOpen = () => {
3493
+ handleSelectOpenChange(!isSelectOpen);
3494
+ };
3495
+ const handleSelect = (option) => {
3496
+ if (controlledSelectValue === undefined) {
3497
+ setUncontrolledSelectValue(option.value);
3498
+ }
3499
+ onSelectChange?.(option.value, option);
3500
+ setIsSelectOpen(false);
3501
+ };
3502
+ const updateDropdownPlacement = React.useCallback(() => {
3503
+ if (typeof window === "undefined")
3504
+ return;
3505
+ const trigger = selectRef.current;
3506
+ if (!trigger)
3507
+ return;
3508
+ const triggerRect = trigger.getBoundingClientRect();
3509
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
3510
+ const spaceAbove = triggerRect.top;
3511
+ const dropdownHeight = dropdownContainerRef.current
3512
+ ? dropdownContainerRef.current.offsetHeight
3513
+ : 0;
3514
+ if (dropdownHeight === 0) {
3515
+ setDropdownPlacement(spaceBelow >= spaceAbove ? "bottom" : "top");
3516
+ return;
3517
+ }
3518
+ if (spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove) {
3519
+ setDropdownPlacement("bottom");
3520
+ }
3521
+ else {
3522
+ setDropdownPlacement("top");
3523
+ }
3524
+ }, []);
3525
+ const attachDropdownListeners = React.useCallback(() => {
3526
+ if (!isSelectOpen)
3527
+ return;
3528
+ if (typeof window === "undefined")
3529
+ return;
3530
+ let rafId = requestAnimationFrame(updateDropdownPlacement);
3531
+ const handleUpdate = () => updateDropdownPlacement();
3532
+ window.addEventListener("resize", handleUpdate);
3533
+ window.addEventListener("scroll", handleUpdate, true);
3534
+ return () => {
3535
+ cancelAnimationFrame(rafId);
3536
+ window.removeEventListener("resize", handleUpdate);
3537
+ window.removeEventListener("scroll", handleUpdate, true);
3538
+ };
3539
+ }, [isSelectOpen, updateDropdownPlacement]);
3540
+ React.useEffect(() => {
3541
+ const detach = attachDropdownListeners();
3542
+ return () => {
3543
+ detach?.();
3544
+ };
3545
+ }, [attachDropdownListeners]);
3546
+ React.useEffect(() => {
3547
+ if (isSelectOpen) {
3548
+ updateDropdownPlacement();
3549
+ }
3550
+ }, [isSelectOpen, selectOptions.length, updateDropdownPlacement]);
3551
+ // Close dropdown when clicking outside
3552
+ React.useEffect(() => {
3553
+ const handleClickOutside = (event) => {
3554
+ const target = event.target;
3555
+ if (selectRef.current &&
3556
+ !selectRef.current.contains(target) &&
3557
+ dropdownContainerRef.current &&
3558
+ !dropdownContainerRef.current.contains(target)) {
3559
+ handleSelectOpenChange(false);
3560
+ }
3561
+ };
3562
+ if (isSelectOpen) {
3563
+ document.addEventListener("mousedown", handleClickOutside);
3564
+ return () => {
3565
+ document.removeEventListener("mousedown", handleClickOutside);
3566
+ };
3567
+ }
3568
+ }, [isSelectOpen]);
3569
+ // Close on escape key
3570
+ React.useEffect(() => {
3571
+ const handleEscape = (event) => {
3572
+ if (event.key === "Escape") {
3573
+ handleSelectOpenChange(false);
3574
+ }
3575
+ };
3576
+ if (isSelectOpen) {
3577
+ document.addEventListener("keydown", handleEscape);
3578
+ return () => {
3579
+ document.removeEventListener("keydown", handleEscape);
3580
+ };
3581
+ }
3582
+ }, [isSelectOpen]);
3583
+ // Transform options to dropdown menu items
3584
+ const menuItems = selectOptions.map((option) => ({
3585
+ value: option.value,
3586
+ label: option.label ?? String(option.value),
3587
+ description: option.description,
3588
+ leadingIcon: option.leadingIcon,
3589
+ trailingIcon: option.trailingIcon,
3590
+ isDisabled: option.isDisabled,
3591
+ variant: option.variant,
3592
+ onClick: () => handleSelect(option),
3593
+ }));
3594
+ const widthStyle = selectMenuWidth === "full"
3595
+ ? "100%"
3596
+ : selectMenuWidth === "auto"
3597
+ ? "auto"
3598
+ : selectMenuWidth;
3599
+ const sizeConfig = {
3600
+ small: {
3601
+ gap: "gap-2",
3602
+ },
3603
+ medium: {
3604
+ gap: "gap-2",
3605
+ },
3606
+ large: {
3607
+ gap: "gap-3",
3608
+ },
3609
+ };
3610
+ // Create the select suffix component
3611
+ const selectSuffix = (jsxs("div", { className: "relative flex items-center h-full", children: [jsxs("div", { ref: selectRef, className: cn(selectTriggerVariants({
3612
+ size,
3613
+ validationState: currentValidationState,
3614
+ isDisabled,
3615
+ }), "border-l border-action-outline-neutral-faded pl-2 ml-2 h-full flex items-center", selectTriggerClassName), onClick: !isDisabled ? toggleSelectOpen : undefined, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isSelectOpen, "aria-disabled": isDisabled, children: [jsx("span", { className: cn("text-left truncate max-w-[120px] whitespace-nowrap", !selectedOption && "text-surface-ink-neutral-muted", isDisabled && "text-surface-ink-neutral-disabled"), children: selectedOption?.label || selectPlaceholder }), jsx(ChevronDown, { className: cn("shrink-0 transition-transform", size === "small"
3616
+ ? "w-3 h-3"
3617
+ : size === "medium"
3618
+ ? "w-3.5 h-3.5"
3619
+ : "w-4 h-4", isDisabled
3620
+ ? "text-surface-ink-neutral-disabled"
3621
+ : currentValidationState === "positive"
3622
+ ? "text-feedback-ink-positive-intense"
3623
+ : currentValidationState === "negative"
3624
+ ? "text-feedback-ink-negative-subtle"
3625
+ : "text-surface-ink-neutral-muted", isSelectOpen && "transform rotate-180") })] }), isSelectOpen && !isDisabled && (jsx("div", { ref: dropdownContainerRef, className: cn("absolute z-50 right-0", dropdownPlacement === "bottom"
3626
+ ? "top-full mt-1"
3627
+ : "bottom-full mb-1"), children: jsx(DropdownMenu, { items: menuItems, sectionHeading: selectSectionHeading, isEmpty: selectOptions.length === 0, emptyTitle: selectEmptyTitle, emptyDescription: selectEmptyDescription, emptyIcon: selectEmptyIcon, disableFooter: true, onClose: () => handleSelectOpenChange(false), className: selectMenuClassName, width: widthStyle }) }))] }));
3628
+ return (jsxs("div", { ref: componentRef, className: cn("w-full flex flex-col", sizeConfig[size].gap, containerClassName), children: [label && (jsx(FormHeader, { label: label, size: size, isRequired: isRequired, isOptional: isOptional, infoHeading: textFieldProps.infoHeading, infoDescription: textFieldProps.infoDescription, LinkComponent: textFieldProps.LinkComponent, linkText: textFieldProps.linkText, linkHref: textFieldProps.linkHref, onLinkClick: textFieldProps.onLinkClick, htmlFor: textFieldProps.id, className: "mb-2", labelClassName: labelClassName })), jsx(TextField, { ref: ref, value: textValue, onChange: handleTextChange, suffix: selectSuffix, size: size, validationState: currentValidationState, isDisabled: isDisabled, isRequired: isRequired, isOptional: isOptional, containerClassName: "gap-0", className: className, inputClassName: inputClassName, ...textFieldProps }), jsx(FormFooter, { helperText: displayHelperText, validationState: currentValidationState === "none"
3629
+ ? "default"
3630
+ : currentValidationState, size: size, isDisabled: isDisabled, className: "mt-1" })] }));
3631
+ });
3632
+ SelectTextField.displayName = "SelectTextField";
3633
+
3424
3634
  const switchVariants = cva("relative inline-flex items-center shrink-0 cursor-pointer rounded-full transition-all duration-200", {
3425
3635
  variants: {
3426
3636
  size: {
@@ -4013,5 +4223,356 @@ const TextArea = React.forwardRef(({ label, helperText, errorText, successText,
4013
4223
  });
4014
4224
  TextArea.displayName = "TextArea";
4015
4225
 
4016
- export { Alert, Amount, Avatar, AvatarCell, Badge, Button, ButtonGroup, Checkbox, Counter, DatePicker, Divider, Dropdown, DropdownMenu, FormFooter, FormHeader, Icon, IconButton, IconCell, Link, ListItem, Modal, NumberCell, Pagination, Radio, SearchableDropdown, Select, Skeleton, SlotCell, SpacerCell, Switch, TabItem, Table, TableDetailPanel, Tabs, Text, TextArea, TextField, Tooltip, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, checkboxVariants, cn, counterVariants, datePickerVariants, dropdownVariants, getAvailableIcons, hasIcon, iconButtonVariants, iconRegistry, linkVariants, listItemVariants, paginationVariants, radioVariants, selectVariants, switchVariants, tableCellVariants, tableHeaderVariants, tableVariants, textAreaVariants, textFieldVariants, tooltipVariants };
4226
+ const uploadBoxVariants = cva("relative flex flex-col items-center justify-center border-2 border-dashed rounded-large transition-all font-display", {
4227
+ variants: {
4228
+ size: {
4229
+ small: "min-h-[120px] p-4 gap-2",
4230
+ medium: "min-h-[160px] p-6 gap-3",
4231
+ large: "min-h-[200px] p-8 gap-4",
4232
+ },
4233
+ validationState: {
4234
+ none: `
4235
+ border-action-outline-neutral-faded
4236
+ hover:border-action-outline-primary-hover
4237
+ focus-within:border-action-outline-primary-hover
4238
+ focus-within:ring-2
4239
+ ring-action-outline-primary-faded-hover
4240
+ bg-surface-fill-neutral-intense`,
4241
+ positive: `
4242
+ border-action-outline-positive-default
4243
+ focus-within:border-action-outline-positive-hover
4244
+ focus-within:ring-2
4245
+ ring-action-outline-positive-faded-hover
4246
+ bg-surface-fill-neutral-intense`,
4247
+ negative: `
4248
+ border-action-outline-negative-default
4249
+ focus-within:border-action-outline-negative-hover
4250
+ focus-within:ring-2
4251
+ ring-action-outline-negative-faded-hover
4252
+ bg-surface-fill-neutral-intense`,
4253
+ },
4254
+ isDisabled: {
4255
+ true: `
4256
+ border-[var(--border-width-thinner)]
4257
+ hover:border-action-outline-neutral-disabled
4258
+ border-action-outline-neutral-disabled
4259
+ bg-surface-fill-neutral-intense
4260
+ cursor-not-allowed
4261
+ opacity-60`,
4262
+ false: "cursor-pointer",
4263
+ },
4264
+ isDragging: {
4265
+ true: "border-action-outline-primary-hover bg-action-fill-primary-faded",
4266
+ false: "",
4267
+ },
4268
+ },
4269
+ defaultVariants: {
4270
+ size: "medium",
4271
+ validationState: "none",
4272
+ isDisabled: false,
4273
+ isDragging: false,
4274
+ },
4275
+ });
4276
+ const formatFileSize = (bytes) => {
4277
+ if (bytes === 0)
4278
+ return "0 Bytes";
4279
+ const k = 1024;
4280
+ const sizes = ["Bytes", "KB", "MB", "GB"];
4281
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
4282
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
4283
+ };
4284
+ const isImageFile = (file, url) => {
4285
+ if (file) {
4286
+ return file.type.startsWith("image/");
4287
+ }
4288
+ if (url) {
4289
+ const lowerUrl = url.toLowerCase();
4290
+ return (lowerUrl.match(/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?.*)?$/i) !== null ||
4291
+ url.match(/data:image\//) !== null);
4292
+ }
4293
+ return false;
4294
+ };
4295
+ const isPdfFile = (file, url) => {
4296
+ if (file) {
4297
+ return file.type === "application/pdf";
4298
+ }
4299
+ if (url) {
4300
+ return url.toLowerCase().match(/\.pdf(\?.*)?$/i) !== null;
4301
+ }
4302
+ return false;
4303
+ };
4304
+ const getFileNameFromUrl = (url) => {
4305
+ try {
4306
+ const urlObj = new URL(url);
4307
+ const pathname = urlObj.pathname;
4308
+ const fileName = pathname.split("/").pop() || "file";
4309
+ return decodeURIComponent(fileName);
4310
+ }
4311
+ catch {
4312
+ // If URL parsing fails, try to extract from the string
4313
+ const parts = url.split("/");
4314
+ const lastPart = parts[parts.length - 1] || "file";
4315
+ return decodeURIComponent(lastPart.split("?")[0]);
4316
+ }
4317
+ };
4318
+ // Component to handle image preview with error fallback
4319
+ const ImagePreview = ({ src, alt }) => {
4320
+ const [hasError, setHasError] = React.useState(false);
4321
+ if (hasError) {
4322
+ return (jsx("div", { className: "shrink-0 w-16 h-16 rounded-medium flex items-center justify-center bg-surface-fill-neutral-subtle", children: jsx(Icon, { name: "file", size: 24, className: "text-surface-ink-neutral-subtle" }) }));
4323
+ }
4324
+ return (jsx("div", { className: "shrink-0 w-16 h-16 rounded-medium overflow-hidden ", children: jsx("img", { src: src, alt: alt, className: "w-full h-full object-cover", onError: () => setHasError(true) }) }));
4325
+ };
4326
+ const UploadBox = React.forwardRef(({ label, helperText, errorText, successText, size = "medium", validationState = "none", isDisabled = false, isRequired = false, isOptional = false, accept, multiple = false, maxSize, maxFiles, value, onChange, onFileRemove, containerClassName, labelClassName, uploadAreaClassName, previewClassName, infoHeading, infoDescription, LinkComponent, linkText, linkHref, onLinkClick, uploadText, dragText, showPreview = true, className, ...props }, ref) => {
4327
+ const fileInputRef = React.useRef(null);
4328
+ const [isDragging, setIsDragging] = React.useState(false);
4329
+ const [uploadedFiles, setUploadedFiles] = React.useState([]);
4330
+ const [error, setError] = React.useState(null);
4331
+ // Initialize uploaded files from value prop
4332
+ React.useEffect(() => {
4333
+ if (value) {
4334
+ const filesArray = Array.isArray(value) ? value : [value];
4335
+ const processedFiles = filesArray.map((item) => {
4336
+ if (item instanceof File) {
4337
+ const uploadedFile = {
4338
+ file: item,
4339
+ name: item.name,
4340
+ size: item.size,
4341
+ id: `${item.name}-${item.size}-${item.lastModified}`,
4342
+ };
4343
+ if (isImageFile(item) && showPreview) {
4344
+ uploadedFile.preview = URL.createObjectURL(item);
4345
+ }
4346
+ return uploadedFile;
4347
+ }
4348
+ else if (typeof item === "string") {
4349
+ // It's a URL string
4350
+ const uploadedFile = {
4351
+ url: item,
4352
+ name: getFileNameFromUrl(item),
4353
+ id: `url-${item}-${Date.now()}`,
4354
+ };
4355
+ // Always set preview for URLs when showPreview is true
4356
+ // The rendering logic will determine if it's an image or PDF
4357
+ if (showPreview) {
4358
+ uploadedFile.preview = item;
4359
+ }
4360
+ return uploadedFile;
4361
+ }
4362
+ else {
4363
+ // It's already an UploadedFile
4364
+ const uploadedFile = item;
4365
+ // If it has a URL but no preview set, set it
4366
+ if (uploadedFile.url && !uploadedFile.preview && showPreview) {
4367
+ uploadedFile.preview = uploadedFile.url;
4368
+ }
4369
+ return uploadedFile;
4370
+ }
4371
+ });
4372
+ setUploadedFiles(processedFiles);
4373
+ }
4374
+ else {
4375
+ setUploadedFiles([]);
4376
+ }
4377
+ }, [value, showPreview]);
4378
+ // Cleanup preview URLs (only revoke object URLs, not regular URLs)
4379
+ React.useEffect(() => {
4380
+ return () => {
4381
+ uploadedFiles.forEach((uploadedFile) => {
4382
+ // Only revoke object URLs (created with URL.createObjectURL)
4383
+ // Regular URLs (http/https) should not be revoked
4384
+ if (uploadedFile.preview &&
4385
+ uploadedFile.preview.startsWith("blob:")) {
4386
+ URL.revokeObjectURL(uploadedFile.preview);
4387
+ }
4388
+ });
4389
+ };
4390
+ }, [uploadedFiles]);
4391
+ const processFiles = (files) => {
4392
+ const fileArray = Array.from(files);
4393
+ const validFiles = [];
4394
+ let currentError = null;
4395
+ // Check max files limit
4396
+ if (maxFiles && uploadedFiles.length + fileArray.length > maxFiles) {
4397
+ currentError = `Maximum ${maxFiles} file(s) allowed`;
4398
+ setError(currentError);
4399
+ return validFiles;
4400
+ }
4401
+ fileArray.forEach((file) => {
4402
+ // Check file size
4403
+ if (maxSize && file.size > maxSize) {
4404
+ currentError = `File "${file.name}" exceeds maximum size of ${formatFileSize(maxSize)}`;
4405
+ setError(currentError);
4406
+ return;
4407
+ }
4408
+ // Check if file type is accepted
4409
+ if (accept) {
4410
+ const acceptedTypes = accept.split(",").map((type) => type.trim());
4411
+ const isAccepted = acceptedTypes.some((type) => {
4412
+ if (type.startsWith(".")) {
4413
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
4414
+ }
4415
+ if (type.includes("/*")) {
4416
+ const baseType = type.split("/")[0];
4417
+ return file.type.startsWith(baseType + "/");
4418
+ }
4419
+ return file.type === type;
4420
+ });
4421
+ if (!isAccepted) {
4422
+ currentError = `File type "${file.type}" is not accepted`;
4423
+ setError(currentError);
4424
+ return;
4425
+ }
4426
+ }
4427
+ validFiles.push(file);
4428
+ });
4429
+ if (currentError) {
4430
+ setError(currentError);
4431
+ }
4432
+ else {
4433
+ setError(null);
4434
+ }
4435
+ return validFiles;
4436
+ };
4437
+ const handleFiles = (files) => {
4438
+ if (files.length === 0)
4439
+ return;
4440
+ const newUploadedFiles = files.map((file) => {
4441
+ const uploadedFile = {
4442
+ file,
4443
+ id: `${file.name}-${file.size}-${file.lastModified}-${Date.now()}`,
4444
+ };
4445
+ if (isImageFile(file) && showPreview) {
4446
+ uploadedFile.preview = URL.createObjectURL(file);
4447
+ }
4448
+ return uploadedFile;
4449
+ });
4450
+ const updatedFiles = multiple
4451
+ ? [...uploadedFiles, ...newUploadedFiles]
4452
+ : newUploadedFiles;
4453
+ setUploadedFiles(updatedFiles);
4454
+ // Call onChange with File objects only (filter out URL-only items)
4455
+ if (onChange) {
4456
+ const filesToReturn = updatedFiles
4457
+ .map((uf) => uf.file)
4458
+ .filter((file) => file !== undefined);
4459
+ onChange(multiple ? filesToReturn : filesToReturn[0] || null);
4460
+ }
4461
+ };
4462
+ const handleFileInputChange = (e) => {
4463
+ if (e.target.files && e.target.files.length > 0) {
4464
+ const validFiles = processFiles(e.target.files);
4465
+ if (validFiles.length > 0) {
4466
+ handleFiles(validFiles);
4467
+ }
4468
+ // Reset input value to allow selecting the same file again
4469
+ e.target.value = "";
4470
+ }
4471
+ };
4472
+ const handleDragOver = (e) => {
4473
+ e.preventDefault();
4474
+ e.stopPropagation();
4475
+ if (!isDisabled) {
4476
+ setIsDragging(true);
4477
+ }
4478
+ };
4479
+ const handleDragLeave = (e) => {
4480
+ e.preventDefault();
4481
+ e.stopPropagation();
4482
+ setIsDragging(false);
4483
+ };
4484
+ const handleDrop = (e) => {
4485
+ e.preventDefault();
4486
+ e.stopPropagation();
4487
+ setIsDragging(false);
4488
+ if (isDisabled)
4489
+ return;
4490
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
4491
+ const validFiles = processFiles(e.dataTransfer.files);
4492
+ if (validFiles.length > 0) {
4493
+ handleFiles(validFiles);
4494
+ }
4495
+ }
4496
+ };
4497
+ const handleClick = () => {
4498
+ if (!isDisabled && fileInputRef.current) {
4499
+ fileInputRef.current.click();
4500
+ }
4501
+ };
4502
+ const handleRemoveFile = (e, uploadedFile) => {
4503
+ e.stopPropagation();
4504
+ if (isDisabled)
4505
+ return;
4506
+ // Revoke preview URL if exists
4507
+ if (uploadedFile.preview) {
4508
+ URL.revokeObjectURL(uploadedFile.preview);
4509
+ }
4510
+ const updatedFiles = uploadedFiles.filter((f) => f.id !== uploadedFile.id);
4511
+ setUploadedFiles(updatedFiles);
4512
+ if (onFileRemove) {
4513
+ onFileRemove(uploadedFile);
4514
+ }
4515
+ if (onChange) {
4516
+ // Only return File objects (filter out URL-only items)
4517
+ const filesToReturn = updatedFiles
4518
+ .map((uf) => uf.file)
4519
+ .filter((file) => file !== undefined);
4520
+ onChange(multiple ? filesToReturn : filesToReturn[0] || null);
4521
+ }
4522
+ };
4523
+ // Determine which helper text to show
4524
+ const displayHelperText = errorText || successText || helperText || error;
4525
+ const currentValidationState = errorText
4526
+ ? "negative"
4527
+ : successText
4528
+ ? "positive"
4529
+ : validationState;
4530
+ const sizeConfig = {
4531
+ small: {
4532
+ gap: "gap-2",
4533
+ iconSize: 16,
4534
+ textSize: "text-body-small-medium",
4535
+ },
4536
+ medium: {
4537
+ gap: "gap-2",
4538
+ iconSize: 16,
4539
+ textSize: "text-body-small-medium",
4540
+ },
4541
+ large: {
4542
+ gap: "gap-3",
4543
+ iconSize: 16,
4544
+ textSize: "text-body-small-medium",
4545
+ },
4546
+ };
4547
+ const config = sizeConfig[size];
4548
+ const defaultUploadText = "Click to upload or drag and drop";
4549
+ const defaultDragText = "Drop files here";
4550
+ 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(uploadBoxVariants({
4551
+ size,
4552
+ validationState: currentValidationState,
4553
+ isDisabled,
4554
+ isDragging,
4555
+ }), uploadAreaClassName, className), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleClick, children: [jsx("input", { ref: fileInputRef, type: "file", accept: accept, multiple: multiple, disabled: isDisabled, onChange: handleFileInputChange, className: "hidden", "aria-label": label || "File upload" }), uploadedFiles.length === 0 ? (jsxs("div", { className: "flex flex-col items-center justify-center gap-2 text-center", children: [jsx("div", { className: "flex items-center justify-center w-8 h-8 rounded-full bg-surface-fill-neutral-subtle", children: jsx(Icon, { name: "upload", size: config.iconSize }) }), jsxs("div", { className: "flex flex-col gap-1", children: [jsx(Text, { variant: "body", size: "small", weight: "medium", color: isDisabled ? "disabled" : "default", children: isDragging
4556
+ ? dragText || defaultDragText
4557
+ : uploadText || defaultUploadText }), accept && (jsxs(Text, { variant: "caption", size: "small", color: isDisabled ? "disabled" : "muted", children: ["Accepted: ", accept] })), maxSize && (jsxs(Text, { variant: "caption", size: "small", color: isDisabled ? "disabled" : "muted", children: ["Max size: ", formatFileSize(maxSize)] }))] })] })) : (jsxs("div", { className: cn("w-full flex flex-col gap-3", previewClassName), children: [uploadedFiles.map((uploadedFile) => {
4558
+ const { file, url, preview, name, size, id } = uploadedFile;
4559
+ const fileName = name ||
4560
+ file?.name ||
4561
+ (url ? getFileNameFromUrl(url) : "Unknown file");
4562
+ const fileSize = size || file?.size;
4563
+ const canPreview = showPreview && preview;
4564
+ const isImage = isImageFile(file, url);
4565
+ const isPdf = isPdfFile(file, url);
4566
+ // For URLs, try to show as image if not explicitly a PDF
4567
+ // If it's a URL without extension, assume it might be an image
4568
+ // Always try to show URLs as images unless they're explicitly PDFs
4569
+ const shouldShowAsImage = canPreview && preview && (isImage || (url && !isPdf));
4570
+ return (jsxs("div", { className: "flex items-start gap-3 p-3 border border-surface-outline-neutral-muted rounded-large bg-surface-fill-neutral-intense", children: [shouldShowAsImage ? (jsx(ImagePreview, { src: preview, alt: fileName })) : canPreview && isPdf ? (jsx("div", { className: "shrink-0 w-16 h-16 rounded-medium border border-action-outline-neutral-faded flex items-center justify-center bg-surface-fill-neutral-subtle", children: jsx(Icon, { name: "file", size: 24, className: "text-surface-ink-neutral-subtle" }) })) : (jsx("div", { className: "shrink-0 w-16 h-16 rounded-medium border border-action-outline-neutral-faded flex items-center justify-center bg-surface-fill-neutral-subtle", children: jsx(Icon, { name: "file", size: 24, className: "text-surface-ink-neutral-subtle" }) })), jsxs("div", { className: "flex-1 min-w-0 flex flex-col gap-1", children: [jsx("div", { className: "flex items-center gap-2", children: jsx(Text, { variant: "body", size: "small", weight: "medium", color: isDisabled ? "disabled" : "default", as: "span", className: "truncate", title: fileName, children: fileName }) }), fileSize !== undefined && (jsx(Text, { variant: "caption", size: "small", color: isDisabled ? "disabled" : "muted", as: "span", children: formatFileSize(fileSize) }))] }), !isDisabled && (jsx(IconButton, { icon: "close", size: "xsmall", onClick: (e) => handleRemoveFile(e, uploadedFile), "aria-label": `Remove ${fileName}` }))] }, id));
4571
+ }), multiple && !isDisabled && (jsx(Button, { variant: "tertiary", color: "primary", size: "small", onClick: handleClick, leadingIcon: jsx(Icon, { name: "add", size: 16 }), className: "mt-2 w-fit", children: "Add more files" }))] }))] }), jsx(FormFooter, { helperText: displayHelperText || undefined, validationState: currentValidationState === "none"
4572
+ ? "default"
4573
+ : currentValidationState, size: size, isDisabled: isDisabled, className: "mt-1" })] }));
4574
+ });
4575
+ UploadBox.displayName = "UploadBox";
4576
+
4577
+ export { Alert, Amount, Avatar, AvatarCell, Badge, Button, ButtonGroup, Checkbox, Counter, DatePicker, Divider, Dropdown, DropdownMenu, FormFooter, FormHeader, Icon, IconButton, IconCell, Link, ListItem, Modal, NumberCell, Pagination, Radio, SearchableDropdown, Select, SelectTextField, Skeleton, SlotCell, SpacerCell, Switch, TabItem, Table, TableDetailPanel, Tabs, Text, TextArea, TextField, Tooltip, UploadBox, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, checkboxVariants, cn, counterVariants, datePickerVariants, dropdownVariants, getAvailableIcons, hasIcon, iconButtonVariants, iconRegistry, linkVariants, listItemVariants, paginationVariants, radioVariants, selectTriggerVariants, selectVariants, switchVariants, tableCellVariants, tableHeaderVariants, tableVariants, textAreaVariants, textFieldVariants, tooltipVariants, uploadBoxVariants };
4017
4578
  //# sourceMappingURL=index.esm.js.map