infinity-ui-elements 1.8.26 → 1.8.28

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
@@ -3201,8 +3201,21 @@ const defaultFilter = (item, query) => {
3201
3201
  return (item.label?.toLowerCase()?.includes(searchQuery) ||
3202
3202
  (item.description?.toLowerCase()?.includes(searchQuery) ?? false));
3203
3203
  };
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);
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, value: controlledValue, defaultValue, onChange, ...textFieldProps }, ref) => {
3205
+ // Find the selected item based on value/defaultValue
3206
+ const findSelectedItem = React.useCallback((val) => {
3207
+ if (val === undefined || val === "")
3208
+ return undefined;
3209
+ return items.find((item) => item.value === val);
3210
+ }, [items]);
3211
+ // Initialize uncontrolled value state
3212
+ const initialValue = controlledValue !== undefined ? controlledValue : defaultValue;
3213
+ const initialSelectedItem = findSelectedItem(initialValue);
3214
+ const initialSearchValue = initialSelectedItem
3215
+ ? initialSelectedItem.label
3216
+ : defaultSearchValue;
3217
+ const [uncontrolledSearchValue, setUncontrolledSearchValue] = React.useState(initialSearchValue);
3218
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
3206
3219
  const [isOpen, setIsOpen] = React.useState(false);
3207
3220
  const [focusedIndex, setFocusedIndex] = React.useState(-1);
3208
3221
  const [isInsideModal, setIsInsideModal] = React.useState(false);
@@ -3215,6 +3228,33 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
3215
3228
  width: 0,
3216
3229
  });
3217
3230
  React.useImperativeHandle(ref, () => inputRef.current);
3231
+ // Determine current value (controlled or uncontrolled)
3232
+ const value = controlledValue !== undefined ? controlledValue : uncontrolledValue;
3233
+ // Sync search value when value prop changes
3234
+ React.useEffect(() => {
3235
+ const selectedItem = findSelectedItem(value);
3236
+ if (selectedItem) {
3237
+ const newSearchValue = selectedItem.label;
3238
+ if (controlledSearchValue === undefined) {
3239
+ setUncontrolledSearchValue(newSearchValue);
3240
+ }
3241
+ // If controlled, we still need to call onSearchChange to notify parent
3242
+ // but only if the search value is different
3243
+ if (controlledSearchValue !== undefined &&
3244
+ controlledSearchValue !== newSearchValue) {
3245
+ onSearchChange?.(newSearchValue);
3246
+ }
3247
+ }
3248
+ else if (value === undefined || value === "") {
3249
+ // If value is cleared, clear search value too
3250
+ if (controlledSearchValue === undefined) {
3251
+ setUncontrolledSearchValue("");
3252
+ }
3253
+ else {
3254
+ onSearchChange?.("");
3255
+ }
3256
+ }
3257
+ }, [value, findSelectedItem, controlledSearchValue, onSearchChange]);
3218
3258
  // Check if dropdown is inside a modal
3219
3259
  React.useEffect(() => {
3220
3260
  if (isOpen && dropdownRef.current) {
@@ -3256,6 +3296,15 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
3256
3296
  setUncontrolledSearchValue(newValue);
3257
3297
  }
3258
3298
  onSearchChange?.(newValue);
3299
+ // If user is typing and the search value no longer matches the selected item's label,
3300
+ // clear the value to indicate no item is selected
3301
+ const selectedItem = findSelectedItem(value);
3302
+ if (selectedItem && selectedItem.label !== newValue) {
3303
+ if (controlledValue === undefined) {
3304
+ setUncontrolledValue(undefined);
3305
+ }
3306
+ onChange?.(undefined, undefined);
3307
+ }
3259
3308
  // Show dropdown if minimum search length is met
3260
3309
  if (newValue.length >= minSearchLength) {
3261
3310
  setIsOpen(true);
@@ -3271,9 +3320,19 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
3271
3320
  };
3272
3321
  const handleItemSelect = (item) => {
3273
3322
  onItemSelect?.(item);
3323
+ // Update search value
3274
3324
  if (controlledSearchValue === undefined) {
3275
3325
  setUncontrolledSearchValue(item.label);
3276
3326
  }
3327
+ else {
3328
+ onSearchChange?.(item.label);
3329
+ }
3330
+ // Update value (controlled or uncontrolled)
3331
+ if (controlledValue === undefined) {
3332
+ setUncontrolledValue(item.value);
3333
+ }
3334
+ // Call onChange callback
3335
+ onChange?.(item.value, item);
3277
3336
  setIsOpen(false);
3278
3337
  inputRef.current?.focus();
3279
3338
  };
@@ -3970,6 +4029,7 @@ function TableComponent({ className, wrapperClassName, containerClassName, varia
3970
4029
  maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
3971
4030
  };
3972
4031
  }, [maxHeight]);
4032
+ // Resolve loading state: prefer 'loading' prop, fallback to 'isLoading' for backward compatibility
3973
4033
  const resolvedLoading = typeof loading === "boolean" ? loading : Boolean(isLoading);
3974
4034
  const skeletonRowCount = loadingSkeletonRows ?? 5;
3975
4035
  const sizeKey = size || "medium";
@@ -4020,6 +4080,7 @@ function TableComponent({ className, wrapperClassName, containerClassName, varia
4020
4080
  onRowClick(row);
4021
4081
  }
4022
4082
  }, [onRowClick]);
4083
+ // ==================== Render Helpers ====================
4023
4084
  const renderEmptyState = () => {
4024
4085
  if (emptyComponent)
4025
4086
  return emptyComponent;