infinity-ui-elements 1.8.44 → 1.8.46
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/Dropdown.d.ts.map +1 -1
- package/dist/components/Dropdown/Dropdown.stories.d.ts +8 -0
- package/dist/components/Dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/Dropdown/DropdownMenu.d.ts +4 -0
- package/dist/components/Dropdown/DropdownMenu.d.ts.map +1 -1
- package/dist/components/SidePanel/SidePanel.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +117 -9
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +117 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2604,18 +2604,18 @@ const ListItem = React.forwardRef(({ className, type = "single", leadingIcon, ti
|
|
|
2604
2604
|
});
|
|
2605
2605
|
ListItem.displayName = "ListItem";
|
|
2606
2606
|
|
|
2607
|
-
const DropdownMenu = React.forwardRef(({ items = [], customContent, sectionHeading, isLoading = false, isEmpty = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, showChevron = false, emptyIcon, disableFooter = false, showFooter, footerLayout = "horizontal", onClose, focusedIndex = -1, className, width = "auto", }, ref) => {
|
|
2607
|
+
const DropdownMenu = React.forwardRef(({ items = [], customContent, sectionHeading, isLoading = false, isEmpty = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, showChevron = false, emptyIcon, disableFooter = false, showFooter, footerLayout = "horizontal", onClose, focusedIndex = -1, className, width = "auto", maxHeight = "400px", }, ref) => {
|
|
2608
2608
|
const renderContent = () => {
|
|
2609
2609
|
if (isLoading) {
|
|
2610
2610
|
return (jsx("div", { className: "flex flex-col items-center justify-center py-12 px-6", children: jsx(Loader2, { className: "w-12 h-12 text-action-ink-primary-normal mb-4 animate-spin" }) }));
|
|
2611
2611
|
}
|
|
2612
2612
|
if (customContent) {
|
|
2613
|
-
return (jsxs("div", { className: "py-3 px-3
|
|
2613
|
+
return (jsxs("div", { className: "py-3 px-3 overflow-y-auto", style: { maxHeight }, children: [sectionHeading && (jsx(Text, { as: "div", variant: "body", size: "small", weight: "medium", className: "text-body-small-medium text-surface-ink-neutral-muted px-3 py-2 mb-1", children: sectionHeading })), jsx("div", { className: "px-1", children: customContent })] }));
|
|
2614
2614
|
}
|
|
2615
2615
|
if (isEmpty || items.length === 0) {
|
|
2616
2616
|
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 }))] }));
|
|
2617
2617
|
}
|
|
2618
|
-
return (jsxs("div", { className: "py-3 px-3
|
|
2618
|
+
return (jsxs("div", { className: "py-3 px-3 overflow-y-auto", style: { maxHeight }, 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: () => {
|
|
2619
2619
|
item.onClick?.();
|
|
2620
2620
|
onClose?.();
|
|
2621
2621
|
}, containerClassName: cn(index === focusedIndex && "bg-action-fill-primary-faded") }, item.value))) })] }));
|
|
@@ -2647,6 +2647,14 @@ const Dropdown = React.forwardRef(({ className, trigger, items = [], customConte
|
|
|
2647
2647
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
2648
2648
|
const isOpen = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen;
|
|
2649
2649
|
const dropdownRef = React.useRef(null);
|
|
2650
|
+
const menuRef = React.useRef(null);
|
|
2651
|
+
const [menuPosition, setMenuPosition] = React.useState({
|
|
2652
|
+
top: "100%",
|
|
2653
|
+
bottom: "auto",
|
|
2654
|
+
left: "0",
|
|
2655
|
+
right: "auto",
|
|
2656
|
+
maxHeight: "400px",
|
|
2657
|
+
});
|
|
2650
2658
|
const handleOpenChange = (newOpen) => {
|
|
2651
2659
|
if (controlledOpen === undefined) {
|
|
2652
2660
|
setUncontrolledOpen(newOpen);
|
|
@@ -2685,12 +2693,92 @@ const Dropdown = React.forwardRef(({ className, trigger, items = [], customConte
|
|
|
2685
2693
|
};
|
|
2686
2694
|
}
|
|
2687
2695
|
}, [isOpen]);
|
|
2696
|
+
// Calculate and adjust dropdown position to keep it within viewport
|
|
2697
|
+
React.useEffect(() => {
|
|
2698
|
+
if (!isOpen || !dropdownRef.current || !menuRef.current)
|
|
2699
|
+
return;
|
|
2700
|
+
const calculatePosition = () => {
|
|
2701
|
+
const triggerRect = dropdownRef.current.getBoundingClientRect();
|
|
2702
|
+
const menuElement = menuRef.current;
|
|
2703
|
+
// Get menu dimensions (use a temporary measurement if needed)
|
|
2704
|
+
const menuRect = menuElement.getBoundingClientRect();
|
|
2705
|
+
const menuHeight = menuRect.height || 400; // fallback to max-height
|
|
2706
|
+
const menuWidth = menuRect.width;
|
|
2707
|
+
const viewportHeight = window.innerHeight;
|
|
2708
|
+
const viewportWidth = window.innerWidth;
|
|
2709
|
+
const spaceBelow = viewportHeight - triggerRect.bottom;
|
|
2710
|
+
const spaceAbove = triggerRect.top;
|
|
2711
|
+
const spaceRight = viewportWidth - triggerRect.left;
|
|
2712
|
+
const spaceLeft = triggerRect.right;
|
|
2713
|
+
const position = {
|
|
2714
|
+
top: "auto",
|
|
2715
|
+
bottom: "auto",
|
|
2716
|
+
left: "auto",
|
|
2717
|
+
right: "auto",
|
|
2718
|
+
maxHeight: "400px",
|
|
2719
|
+
};
|
|
2720
|
+
// Vertical positioning
|
|
2721
|
+
if (spaceBelow >= menuHeight || spaceBelow >= spaceAbove) {
|
|
2722
|
+
// Position below trigger
|
|
2723
|
+
position.top = "100%";
|
|
2724
|
+
position.bottom = "auto";
|
|
2725
|
+
position.maxHeight = `${Math.min(400, spaceBelow - 16)}px`;
|
|
2726
|
+
}
|
|
2727
|
+
else {
|
|
2728
|
+
// Position above trigger
|
|
2729
|
+
position.top = "auto";
|
|
2730
|
+
position.bottom = "100%";
|
|
2731
|
+
position.maxHeight = `${Math.min(400, spaceAbove - 16)}px`;
|
|
2732
|
+
}
|
|
2733
|
+
// Horizontal positioning
|
|
2734
|
+
if (spaceRight >= menuWidth) {
|
|
2735
|
+
// Align to left edge of trigger
|
|
2736
|
+
position.left = "0";
|
|
2737
|
+
position.right = "auto";
|
|
2738
|
+
}
|
|
2739
|
+
else if (spaceLeft >= menuWidth) {
|
|
2740
|
+
// Align to right edge of trigger
|
|
2741
|
+
position.left = "auto";
|
|
2742
|
+
position.right = "0";
|
|
2743
|
+
}
|
|
2744
|
+
else {
|
|
2745
|
+
// Not enough space on either side, try to center or align based on available space
|
|
2746
|
+
if (triggerRect.left + menuWidth > viewportWidth) {
|
|
2747
|
+
position.left = "auto";
|
|
2748
|
+
position.right = "0";
|
|
2749
|
+
}
|
|
2750
|
+
else {
|
|
2751
|
+
position.left = "0";
|
|
2752
|
+
position.right = "auto";
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
setMenuPosition(position);
|
|
2756
|
+
};
|
|
2757
|
+
// Calculate position after menu is rendered
|
|
2758
|
+
calculatePosition();
|
|
2759
|
+
// Recalculate on window resize or scroll
|
|
2760
|
+
const handleResize = () => calculatePosition();
|
|
2761
|
+
const handleScroll = () => calculatePosition();
|
|
2762
|
+
window.addEventListener("resize", handleResize);
|
|
2763
|
+
window.addEventListener("scroll", handleScroll, true);
|
|
2764
|
+
return () => {
|
|
2765
|
+
window.removeEventListener("resize", handleResize);
|
|
2766
|
+
window.removeEventListener("scroll", handleScroll, true);
|
|
2767
|
+
};
|
|
2768
|
+
}, [isOpen]);
|
|
2688
2769
|
const sizeMap = {
|
|
2689
2770
|
small: "w-64",
|
|
2690
2771
|
medium: "w-80",
|
|
2691
2772
|
large: "w-96",
|
|
2692
2773
|
};
|
|
2693
|
-
return (jsxs("div", { ref: dropdownRef, className: cn("relative inline-block", containerClassName), ...props, children: [trigger && (jsx("div", { onClick: toggleOpen, className: "cursor-pointer", children: trigger })), isOpen && (jsx(
|
|
2774
|
+
return (jsxs("div", { ref: dropdownRef, className: cn("relative inline-block", containerClassName), ...props, children: [trigger && (jsx("div", { onClick: toggleOpen, className: "cursor-pointer", children: trigger })), isOpen && (jsx("div", { ref: menuRef, className: cn("absolute z-50", menuClassName), style: {
|
|
2775
|
+
top: menuPosition.top,
|
|
2776
|
+
bottom: menuPosition.bottom,
|
|
2777
|
+
left: menuPosition.left,
|
|
2778
|
+
right: menuPosition.right,
|
|
2779
|
+
marginTop: menuPosition.top === "100%" ? "0.5rem" : "0",
|
|
2780
|
+
marginBottom: menuPosition.bottom === "100%" ? "0.5rem" : "0",
|
|
2781
|
+
}, children: jsx(DropdownMenu, { ref: ref, items: items, customContent: customContent, sectionHeading: sectionHeading, isLoading: isLoading, isEmpty: isEmpty, emptyTitle: emptyTitle, emptyDescription: emptyDescription, emptyLinkText: emptyLinkText, onEmptyLinkClick: onEmptyLinkClick, primaryButtonText: primaryButtonText, secondaryButtonText: secondaryButtonText, onPrimaryClick: onPrimaryClick, onSecondaryClick: onSecondaryClick, showChevron: showChevron, emptyIcon: emptyIcon, disableFooter: disableFooter, showFooter: showFooter, onClose: () => handleOpenChange(false), className: className, width: sizeMap[size], maxHeight: menuPosition.maxHeight }) }))] }));
|
|
2694
2782
|
});
|
|
2695
2783
|
Dropdown.displayName = "Dropdown";
|
|
2696
2784
|
|
|
@@ -3803,6 +3891,8 @@ SearchableDropdown.displayName = "SearchableDropdown";
|
|
|
3803
3891
|
const SidePanel = React.forwardRef(({ isOpen, onClose, title, titleIcon, description, footer, children, position = "right", width = "medium", showCloseButton = true, headerActions, closeOnOverlayClick = true, closeOnEscape = true, className, contentClassName, headerClassName, bodyClassName, footerClassName, overlayClassName, ariaLabel, ariaDescribedBy, }, ref) => {
|
|
3804
3892
|
const panelRef = React.useRef(null);
|
|
3805
3893
|
const contentRef = ref || panelRef;
|
|
3894
|
+
const [isVisible, setIsVisible] = React.useState(false);
|
|
3895
|
+
const [shouldRender, setShouldRender] = React.useState(false);
|
|
3806
3896
|
// Width configurations
|
|
3807
3897
|
const widthConfig = {
|
|
3808
3898
|
small: "w-80",
|
|
@@ -3810,15 +3900,33 @@ const SidePanel = React.forwardRef(({ isOpen, onClose, title, titleIcon, descrip
|
|
|
3810
3900
|
large: "w-[32rem]",
|
|
3811
3901
|
xlarge: "w-[40rem]",
|
|
3812
3902
|
};
|
|
3903
|
+
// Handle mounting and unmounting with animation
|
|
3904
|
+
React.useEffect(() => {
|
|
3905
|
+
if (isOpen) {
|
|
3906
|
+
setShouldRender(true);
|
|
3907
|
+
// Small delay to trigger animation
|
|
3908
|
+
requestAnimationFrame(() => {
|
|
3909
|
+
setIsVisible(true);
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
else {
|
|
3913
|
+
setIsVisible(false);
|
|
3914
|
+
// Wait for animation to complete before unmounting
|
|
3915
|
+
const timer = setTimeout(() => {
|
|
3916
|
+
setShouldRender(false);
|
|
3917
|
+
}, 300); // Match animation duration
|
|
3918
|
+
return () => clearTimeout(timer);
|
|
3919
|
+
}
|
|
3920
|
+
}, [isOpen]);
|
|
3813
3921
|
// Position-based classes
|
|
3814
3922
|
const positionClasses = {
|
|
3815
3923
|
left: {
|
|
3816
3924
|
container: "justify-start",
|
|
3817
|
-
panel:
|
|
3925
|
+
panel: isVisible ? "animate-slide-in-left" : "animate-slide-out-left",
|
|
3818
3926
|
},
|
|
3819
3927
|
right: {
|
|
3820
3928
|
container: "justify-end",
|
|
3821
|
-
panel:
|
|
3929
|
+
panel: isVisible ? "animate-slide-in-right" : "animate-slide-out-right",
|
|
3822
3930
|
},
|
|
3823
3931
|
};
|
|
3824
3932
|
// Handle escape key
|
|
@@ -3851,14 +3959,14 @@ const SidePanel = React.forwardRef(({ isOpen, onClose, title, titleIcon, descrip
|
|
|
3851
3959
|
onClose();
|
|
3852
3960
|
}
|
|
3853
3961
|
};
|
|
3854
|
-
// Don't render if not open
|
|
3855
|
-
if (!
|
|
3962
|
+
// Don't render if not open and animation is complete
|
|
3963
|
+
if (!shouldRender)
|
|
3856
3964
|
return null;
|
|
3857
3965
|
const hasHeader = title || description;
|
|
3858
3966
|
const widthClass = width in widthConfig
|
|
3859
3967
|
? widthConfig[width]
|
|
3860
3968
|
: width;
|
|
3861
|
-
return (jsxs("div", { className: cn("fixed inset-0 z-10000 flex
|
|
3969
|
+
return (jsxs("div", { className: cn("fixed inset-0 z-10000 flex p-4", positionClasses[position].container, 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 duration-300", isVisible ? "opacity-100" : "opacity-0", overlayClassName), onClick: handleOverlayClick, "aria-hidden": "true" }), jsxs("div", { ref: contentRef, className: cn("relative bg-white shadow-2xl transition-transform duration-300 ease-out rounded-large", "flex flex-col max-w-full h-full overflow-hidden", widthClass, positionClasses[position].panel, contentClassName), children: [hasHeader && (jsxs("div", { className: cn("flex items-center justify-between gap-4 px-7 py-5 shrink-0 border-b border-surface-outline-neutral-subtle", headerClassName), children: [jsxs("div", { className: "flex items-center gap-3 flex-1 min-w-0", children: [titleIcon && (jsx("div", { className: "flex items-center justify-center shrink-0", children: typeof titleIcon === "string" ? (jsx(Icon, { name: titleIcon, size: 20 })) : (titleIcon) })), jsxs("div", { className: "flex flex-col flex-1 min-w-0", children: [title && (jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", className: "truncate", children: title })), description && (jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-0.5", children: description }))] })] }), jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [headerActions, showCloseButton && onClose && (jsx(IconButton, { icon: "close", onClick: onClose, color: "neutral", size: "small", "aria-label": "Close side panel" }))] })] })), !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 side panel" }) })), jsx("div", { className: cn("flex-1 overflow-y-auto px-6", hasHeader ? "py-6" : "pt-6 pb-4", !footer && "pb-6", bodyClassName), children: children }), footer && (jsxs("div", { className: "flex flex-col shrink-0", 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 })] }))] })] }));
|
|
3862
3970
|
});
|
|
3863
3971
|
SidePanel.displayName = "SidePanel";
|
|
3864
3972
|
|