infinity-ui-elements 1.8.27 → 1.8.29

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
@@ -2597,7 +2597,7 @@ const selectVariants = cva("relative flex items-center gap-2 border rounded-larg
2597
2597
  isDisabled: false,
2598
2598
  },
2599
2599
  });
2600
- const Select = React.forwardRef(({ className, options = [], value: controlledValue, defaultValue, onChange, placeholder = "Select an option", label, helperText, errorText, successText, validationState = "none", isDisabled = false, isRequired = false, isOptional = false, isLoading = false, size = "medium", prefix, suffix, showClearButton = false, onClear, containerClassName, labelClassName, triggerClassName, menuClassName, menuWidth = "full", sectionHeading, emptyTitle = "No options available", emptyDescription = "There are no options to select from.", emptyIcon, infoHeading, infoDescription, LinkComponent, linkText, linkHref, onLinkClick, ...props }, ref) => {
2600
+ const Select = React.forwardRef(({ className, options = [], value: controlledValue, defaultValue, onChange, placeholder = "Select an option", label, helperText, errorText, successText, validationState = "none", isDisabled = false, isRequired = false, isOptional = false, isLoading = false, size = "medium", prefix, suffix, showClearButton = false, onClear, showLeadingIcon = false, containerClassName, labelClassName, triggerClassName, menuClassName, menuWidth = "full", sectionHeading, emptyTitle = "No options available", emptyDescription = "There are no options to select from.", emptyIcon, infoHeading, infoDescription, LinkComponent, linkText, linkHref, onLinkClick, ...props }, ref) => {
2601
2601
  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
2602
2602
  const [isOpen, setIsOpen] = React.useState(false);
2603
2603
  const [dropdownPlacement, setDropdownPlacement] = React.useState("bottom");
@@ -2776,13 +2776,19 @@ const Select = React.forwardRef(({ className, options = [], value: controlledVal
2776
2776
  size,
2777
2777
  validationState: currentValidationState,
2778
2778
  isDisabled,
2779
- }), "relative w-full cursor-pointer", className), onClick: !isDisabled && !isLoading ? toggleOpen : undefined, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-disabled": isDisabled, ...props, children: [prefix && (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
2779
+ }), "relative w-full cursor-pointer", className), onClick: !isDisabled && !isLoading ? toggleOpen : undefined, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-disabled": isDisabled, ...props, children: [prefix ? (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
2780
2780
  ? "text-surface-ink-neutral-disabled"
2781
2781
  : currentValidationState === "positive"
2782
2782
  ? "text-feedback-ink-positive-intense"
2783
2783
  : currentValidationState === "negative"
2784
2784
  ? "text-feedback-ink-negative-subtle"
2785
- : "text-surface-ink-neutral-muted"), children: prefix })), jsx("span", { className: cn("flex-1 text-left truncate", !selectedOption && "text-surface-ink-neutral-muted", isDisabled && "text-surface-ink-neutral-disabled"), children: isLoading ? "Loading..." : selectedOption?.label || placeholder }), showClearButton && hasValue && !isDisabled && !isLoading && (jsx("button", { type: "button", onClick: handleClear, className: "shrink-0 flex items-center justify-center text-surface-ink-neutral-muted hover:text-surface-ink-neutral-normal transition-colors", tabIndex: -1, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })), suffix && !showClearButton && (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
2785
+ : "text-surface-ink-neutral-muted"), children: prefix })) : showLeadingIcon && selectedOption?.leadingIcon ? (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
2786
+ ? "text-surface-ink-neutral-disabled"
2787
+ : currentValidationState === "positive"
2788
+ ? "text-feedback-ink-positive-intense"
2789
+ : currentValidationState === "negative"
2790
+ ? "text-feedback-ink-negative-subtle"
2791
+ : "text-surface-ink-neutral-muted"), children: selectedOption.leadingIcon })) : null, jsx("span", { className: cn("flex-1 text-left truncate", !selectedOption && "text-surface-ink-neutral-muted", isDisabled && "text-surface-ink-neutral-disabled"), children: isLoading ? "Loading..." : selectedOption?.label || placeholder }), showClearButton && hasValue && !isDisabled && !isLoading && (jsx("button", { type: "button", onClick: handleClear, className: "shrink-0 flex items-center justify-center text-surface-ink-neutral-muted hover:text-surface-ink-neutral-normal transition-colors", tabIndex: -1, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })), suffix && !showClearButton && (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
2786
2792
  ? "text-surface-ink-neutral-disabled"
2787
2793
  : currentValidationState === "positive"
2788
2794
  ? "text-feedback-ink-positive-intense"
@@ -3201,8 +3207,21 @@ const defaultFilter = (item, query) => {
3201
3207
  return (item.label?.toLowerCase()?.includes(searchQuery) ||
3202
3208
  (item.description?.toLowerCase()?.includes(searchQuery) ?? false));
3203
3209
  };
3204
- const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHeading, isLoading = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText, secondaryButtonText, onPrimaryClick, onSecondaryClick, dropdownWidth = "full", showChevron = false, emptyIcon, disableFooter = false, footerLayout = "horizontal", onSearchChange, onItemSelect, filterFunction = defaultFilter, searchValue: controlledSearchValue, defaultSearchValue = "", dropdownClassName, minSearchLength = 0, showOnFocus = true, showAddNew = false, showAddNewIfDoesNotMatch = true, onAddNew, containerClassName, ...textFieldProps }, ref) => {
3205
- const [uncontrolledSearchValue, setUncontrolledSearchValue] = React.useState(defaultSearchValue);
3210
+ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHeading, isLoading = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText, secondaryButtonText, onPrimaryClick, onSecondaryClick, dropdownWidth = "full", showChevron = false, emptyIcon, disableFooter = false, footerLayout = "horizontal", onSearchChange, onItemSelect, filterFunction = defaultFilter, searchValue: controlledSearchValue, defaultSearchValue = "", dropdownClassName, minSearchLength = 0, showOnFocus = true, showAddNew = false, showAddNewIfDoesNotMatch = true, onAddNew, containerClassName, value: controlledValue, defaultValue, onChange, ...textFieldProps }, ref) => {
3211
+ // Find the selected item based on value/defaultValue
3212
+ const findSelectedItem = React.useCallback((val) => {
3213
+ if (val === undefined || val === "")
3214
+ return undefined;
3215
+ return items.find((item) => item.value === val);
3216
+ }, [items]);
3217
+ // Initialize uncontrolled value state
3218
+ const initialValue = controlledValue !== undefined ? controlledValue : defaultValue;
3219
+ const initialSelectedItem = findSelectedItem(initialValue);
3220
+ const initialSearchValue = initialSelectedItem
3221
+ ? initialSelectedItem.label
3222
+ : defaultSearchValue;
3223
+ const [uncontrolledSearchValue, setUncontrolledSearchValue] = React.useState(initialSearchValue);
3224
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
3206
3225
  const [isOpen, setIsOpen] = React.useState(false);
3207
3226
  const [focusedIndex, setFocusedIndex] = React.useState(-1);
3208
3227
  const [isInsideModal, setIsInsideModal] = React.useState(false);
@@ -3215,6 +3234,33 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
3215
3234
  width: 0,
3216
3235
  });
3217
3236
  React.useImperativeHandle(ref, () => inputRef.current);
3237
+ // Determine current value (controlled or uncontrolled)
3238
+ const value = controlledValue !== undefined ? controlledValue : uncontrolledValue;
3239
+ // Sync search value when value prop changes
3240
+ React.useEffect(() => {
3241
+ const selectedItem = findSelectedItem(value);
3242
+ if (selectedItem) {
3243
+ const newSearchValue = selectedItem.label;
3244
+ if (controlledSearchValue === undefined) {
3245
+ setUncontrolledSearchValue(newSearchValue);
3246
+ }
3247
+ // If controlled, we still need to call onSearchChange to notify parent
3248
+ // but only if the search value is different
3249
+ if (controlledSearchValue !== undefined &&
3250
+ controlledSearchValue !== newSearchValue) {
3251
+ onSearchChange?.(newSearchValue);
3252
+ }
3253
+ }
3254
+ else if (value === undefined || value === "") {
3255
+ // If value is cleared, clear search value too
3256
+ if (controlledSearchValue === undefined) {
3257
+ setUncontrolledSearchValue("");
3258
+ }
3259
+ else {
3260
+ onSearchChange?.("");
3261
+ }
3262
+ }
3263
+ }, [value, findSelectedItem, controlledSearchValue, onSearchChange]);
3218
3264
  // Check if dropdown is inside a modal
3219
3265
  React.useEffect(() => {
3220
3266
  if (isOpen && dropdownRef.current) {
@@ -3256,6 +3302,15 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
3256
3302
  setUncontrolledSearchValue(newValue);
3257
3303
  }
3258
3304
  onSearchChange?.(newValue);
3305
+ // If user is typing and the search value no longer matches the selected item's label,
3306
+ // clear the value to indicate no item is selected
3307
+ const selectedItem = findSelectedItem(value);
3308
+ if (selectedItem && selectedItem.label !== newValue) {
3309
+ if (controlledValue === undefined) {
3310
+ setUncontrolledValue(undefined);
3311
+ }
3312
+ onChange?.(undefined, undefined);
3313
+ }
3259
3314
  // Show dropdown if minimum search length is met
3260
3315
  if (newValue.length >= minSearchLength) {
3261
3316
  setIsOpen(true);
@@ -3271,9 +3326,19 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
3271
3326
  };
3272
3327
  const handleItemSelect = (item) => {
3273
3328
  onItemSelect?.(item);
3329
+ // Update search value
3274
3330
  if (controlledSearchValue === undefined) {
3275
3331
  setUncontrolledSearchValue(item.label);
3276
3332
  }
3333
+ else {
3334
+ onSearchChange?.(item.label);
3335
+ }
3336
+ // Update value (controlled or uncontrolled)
3337
+ if (controlledValue === undefined) {
3338
+ setUncontrolledValue(item.value);
3339
+ }
3340
+ // Call onChange callback
3341
+ onChange?.(item.value, item);
3277
3342
  setIsOpen(false);
3278
3343
  inputRef.current?.focus();
3279
3344
  };