infinity-ui-elements 1.8.2 → 1.8.4
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/DropdownMenu.d.ts +3 -2
- package/dist/components/Dropdown/DropdownMenu.d.ts.map +1 -1
- package/dist/components/Modal/Modal.d.ts +1 -1
- package/dist/components/Modal/Modal.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts +4 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.stories.d.ts +6 -0
- package/dist/components/SearchableDropdown/SearchableDropdown.stories.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +52 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +51 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Slot } from '@radix-ui/react-slot';
|
|
|
5
5
|
import { PulseLoader, ClipLoader } from 'react-spinners';
|
|
6
6
|
import { clsx } from 'clsx';
|
|
7
7
|
import { twMerge } from 'tailwind-merge';
|
|
8
|
-
import { ExternalLink, Loader2, Search,
|
|
8
|
+
import { ExternalLink, Loader2, Search, ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
9
9
|
import { createPortal } from 'react-dom';
|
|
10
10
|
import { flexRender } from '@tanstack/react-table';
|
|
11
11
|
|
|
@@ -1868,10 +1868,10 @@ const DropdownMenu = React.forwardRef(({ items = [], customContent, sectionHeadi
|
|
|
1868
1868
|
if (isEmpty || items.length === 0) {
|
|
1869
1869
|
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 }))] }));
|
|
1870
1870
|
}
|
|
1871
|
-
return (jsxs("div", { className: "py-3 px-3 max-h-[400px] overflow-y-auto", 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.
|
|
1871
|
+
return (jsxs("div", { className: "py-3 px-3 max-h-[400px] overflow-y-auto", 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: () => {
|
|
1872
1872
|
item.onClick?.();
|
|
1873
1873
|
onClose?.();
|
|
1874
|
-
}, containerClassName: cn(index === focusedIndex && "bg-action-fill-primary-faded") }, item.
|
|
1874
|
+
}, containerClassName: cn(index === focusedIndex && "bg-action-fill-primary-faded") }, item.value))) })] }));
|
|
1875
1875
|
};
|
|
1876
1876
|
const widthClass = width === "full" ? "w-full" : width === "auto" ? "w-auto" : "";
|
|
1877
1877
|
const footerVisible = showFooter ?? !disableFooter;
|
|
@@ -2213,7 +2213,7 @@ const Modal = React.forwardRef(({ isOpen, onClose, title, description, footer, c
|
|
|
2213
2213
|
};
|
|
2214
2214
|
// Handle escape key
|
|
2215
2215
|
React.useEffect(() => {
|
|
2216
|
-
if (!isOpen || !closeOnEscape)
|
|
2216
|
+
if (!isOpen || !closeOnEscape || !onClose)
|
|
2217
2217
|
return;
|
|
2218
2218
|
const handleEscape = (e) => {
|
|
2219
2219
|
if (e.key === "Escape") {
|
|
@@ -2237,7 +2237,7 @@ const Modal = React.forwardRef(({ isOpen, onClose, title, description, footer, c
|
|
|
2237
2237
|
}, [isOpen]);
|
|
2238
2238
|
// Handle overlay click
|
|
2239
2239
|
const handleOverlayClick = (e) => {
|
|
2240
|
-
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
2240
|
+
if (closeOnOverlayClick && e.target === e.currentTarget && onClose) {
|
|
2241
2241
|
onClose();
|
|
2242
2242
|
}
|
|
2243
2243
|
};
|
|
@@ -2245,7 +2245,7 @@ const Modal = React.forwardRef(({ isOpen, onClose, title, description, footer, c
|
|
|
2245
2245
|
if (!isOpen)
|
|
2246
2246
|
return null;
|
|
2247
2247
|
const hasHeader = title || description;
|
|
2248
|
-
return (jsxs("div", { className: cn("fixed inset-0 z-50 flex items-center justify-center p-4", className), role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsx("div", { className: cn("absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity", overlayClassName), onClick: handleOverlayClick, "aria-hidden": "true" }), jsxs("div", { ref: contentRef, className: cn("relative w-full bg-white rounded-large shadow-xl transition-all", "flex flex-col max-h-[90vh]", sizeConfig[size], contentClassName), children: [hasHeader && (jsxs("div", { className: cn("flex items-start justify-between gap-4 px-6 pt-6", !description && "pb-4", description && "pb-2", headerClassName), children: [jsxs("div", { className: "flex-1", children: [title && (jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && (jsx(
|
|
2248
|
+
return (jsxs("div", { className: cn("fixed inset-0 z-50 flex items-center justify-center p-4", className), role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsx("div", { className: cn("absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity", overlayClassName), onClick: handleOverlayClick, "aria-hidden": "true" }), jsxs("div", { ref: contentRef, className: cn("relative w-full bg-white rounded-large shadow-xl transition-all", "flex flex-col max-h-[90vh]", sizeConfig[size], contentClassName), children: [hasHeader && (jsxs("div", { className: cn("flex items-start justify-between gap-4 px-6 pt-6", !description && "pb-4", description && "pb-2", headerClassName), children: [jsxs("div", { className: "flex-1", children: [title && (jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && onClose && (jsx(IconButton, { icon: "close", onClick: onClose, color: "neutral", size: "small", "aria-label": "Close modal", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsx("div", { className: "absolute top-4 right-4 z-10", children: jsx(IconButton, { icon: "close", onClick: onClose, color: "neutral", size: "small", "aria-label": "Close modal" }) })), jsx("div", { className: cn("flex-1 overflow-y-auto px-6", hasHeader ? "py-4" : "pt-6 pb-4", !footer && "pb-6", bodyClassName), children: children }), footer && (jsxs("div", { className: "flex flex-col", children: [jsx(Divider, { thickness: "thin", variant: "muted" }), jsx("div", { className: cn("flex items-center justify-end gap-3 px-6 py-4", footerClassName), children: footer })] }))] })] }));
|
|
2249
2249
|
});
|
|
2250
2250
|
Modal.displayName = "Modal";
|
|
2251
2251
|
|
|
@@ -2885,10 +2885,10 @@ TextField.displayName = "TextField";
|
|
|
2885
2885
|
|
|
2886
2886
|
const defaultFilter = (item, query) => {
|
|
2887
2887
|
const searchQuery = query.toLowerCase();
|
|
2888
|
-
return (item.
|
|
2888
|
+
return (item.label.toLowerCase().includes(searchQuery) ||
|
|
2889
2889
|
(item.description?.toLowerCase().includes(searchQuery) ?? false));
|
|
2890
2890
|
};
|
|
2891
|
-
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
|
|
2891
|
+
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, containerClassName, ...textFieldProps }, ref) => {
|
|
2892
2892
|
const [uncontrolledSearchValue, setUncontrolledSearchValue] = React.useState(defaultSearchValue);
|
|
2893
2893
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
2894
2894
|
const [focusedIndex, setFocusedIndex] = React.useState(-1);
|
|
@@ -2937,7 +2937,7 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
2937
2937
|
const handleItemSelect = (item) => {
|
|
2938
2938
|
onItemSelect?.(item);
|
|
2939
2939
|
if (controlledSearchValue === undefined) {
|
|
2940
|
-
setUncontrolledSearchValue(item.
|
|
2940
|
+
setUncontrolledSearchValue(item.label);
|
|
2941
2941
|
}
|
|
2942
2942
|
setIsOpen(false);
|
|
2943
2943
|
inputRef.current?.focus();
|
|
@@ -2948,6 +2948,40 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
2948
2948
|
return items;
|
|
2949
2949
|
return items.filter((item) => filterFunction(item, searchValue));
|
|
2950
2950
|
}, [items, searchValue, filterFunction]);
|
|
2951
|
+
// Add "Add New" option if showAddNew is true and no items match
|
|
2952
|
+
const itemsWithAddNew = React.useMemo(() => {
|
|
2953
|
+
if (!showAddNew || !searchValue || filteredItems.length > 0) {
|
|
2954
|
+
return filteredItems;
|
|
2955
|
+
}
|
|
2956
|
+
const addNewItem = {
|
|
2957
|
+
value: searchValue,
|
|
2958
|
+
label: `+ Add ${searchValue}`,
|
|
2959
|
+
variant: "primary",
|
|
2960
|
+
onClick: () => {
|
|
2961
|
+
const newItem = {
|
|
2962
|
+
value: searchValue,
|
|
2963
|
+
label: searchValue,
|
|
2964
|
+
};
|
|
2965
|
+
onItemSelect?.(newItem);
|
|
2966
|
+
if (controlledSearchValue === undefined) {
|
|
2967
|
+
setUncontrolledSearchValue(searchValue);
|
|
2968
|
+
}
|
|
2969
|
+
setIsOpen(false);
|
|
2970
|
+
inputRef.current?.focus();
|
|
2971
|
+
},
|
|
2972
|
+
};
|
|
2973
|
+
return [addNewItem];
|
|
2974
|
+
}, [
|
|
2975
|
+
showAddNew,
|
|
2976
|
+
searchValue,
|
|
2977
|
+
filteredItems,
|
|
2978
|
+
onItemSelect,
|
|
2979
|
+
controlledSearchValue,
|
|
2980
|
+
]);
|
|
2981
|
+
// Reset focused index when items change
|
|
2982
|
+
React.useEffect(() => {
|
|
2983
|
+
setFocusedIndex(-1);
|
|
2984
|
+
}, [itemsWithAddNew.length]);
|
|
2951
2985
|
// Close dropdown when clicking outside
|
|
2952
2986
|
React.useEffect(() => {
|
|
2953
2987
|
const handleClickOutside = (event) => {
|
|
@@ -2977,7 +3011,7 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
2977
3011
|
switch (e.key) {
|
|
2978
3012
|
case "ArrowDown":
|
|
2979
3013
|
e.preventDefault();
|
|
2980
|
-
setFocusedIndex((prev) => prev <
|
|
3014
|
+
setFocusedIndex((prev) => prev < itemsWithAddNew.length - 1 ? prev + 1 : prev);
|
|
2981
3015
|
break;
|
|
2982
3016
|
case "ArrowUp":
|
|
2983
3017
|
e.preventDefault();
|
|
@@ -2985,8 +3019,8 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
2985
3019
|
break;
|
|
2986
3020
|
case "Enter":
|
|
2987
3021
|
e.preventDefault();
|
|
2988
|
-
if (focusedIndex >= 0 &&
|
|
2989
|
-
handleItemSelect(
|
|
3022
|
+
if (focusedIndex >= 0 && itemsWithAddNew[focusedIndex]) {
|
|
3023
|
+
handleItemSelect(itemsWithAddNew[focusedIndex]);
|
|
2990
3024
|
}
|
|
2991
3025
|
break;
|
|
2992
3026
|
case "Escape":
|
|
@@ -2997,9 +3031,10 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
2997
3031
|
}
|
|
2998
3032
|
};
|
|
2999
3033
|
// Update items with onClick handlers that call handleItemSelect
|
|
3000
|
-
|
|
3034
|
+
// Only add onClick if it doesn't already exist (for Add New items)
|
|
3035
|
+
const itemsWithHandlers = itemsWithAddNew.map((item) => ({
|
|
3001
3036
|
...item,
|
|
3002
|
-
onClick: () => handleItemSelect(item),
|
|
3037
|
+
onClick: item.onClick || (() => handleItemSelect(item)),
|
|
3003
3038
|
}));
|
|
3004
3039
|
const showDropdown = isOpen && searchValue.length >= minSearchLength;
|
|
3005
3040
|
const dropdownMenu = showDropdown && (jsx("div", { ref: menuRef, style: {
|
|
@@ -3008,7 +3043,9 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
3008
3043
|
left: `${position.left}px`,
|
|
3009
3044
|
width: dropdownWidth === "full" ? `${position.width}px` : "auto",
|
|
3010
3045
|
zIndex: 9999,
|
|
3011
|
-
}, children: jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading: sectionHeading, isLoading: isLoading, isEmpty:
|
|
3046
|
+
}, children: 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
|
|
3047
|
+
? true
|
|
3048
|
+
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: dropdownWidth === "full" ? "full" : "auto" }) }));
|
|
3012
3049
|
return (jsxs(Fragment, { children: [jsx("div", { ref: dropdownRef, className: cn("relative", containerClassName), children: jsx(TextField, { ref: inputRef, value: searchValue, onChange: handleSearchChange, onFocus: handleFocus, onKeyDown: handleKeyDown, containerClassName: "mb-0", ...textFieldProps }) }), typeof document !== "undefined" &&
|
|
3013
3050
|
dropdownMenu &&
|
|
3014
3051
|
createPortal(dropdownMenu, document.body)] }));
|