infinity-ui-elements 1.14.2 → 1.14.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/BottomSheet/BottomSheet.d.ts +9 -0
- package/dist/components/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.stories.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +49 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +49 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1450,7 +1450,7 @@ const getSafeAreaInsetBottom = () => {
|
|
|
1450
1450
|
document.body.removeChild(testDiv);
|
|
1451
1451
|
return paddingBottom;
|
|
1452
1452
|
};
|
|
1453
|
-
const BottomSheet = React.forwardRef(({ isOpen, onClose, title, description, footer, children, variant = "default", showCloseButton = true, showDragHandle = true, closeOnOverlayClick = true, closeOnEscape = true, closeOnSwipeDown = true, maxHeight = "90vh", className, contentClassName, headerClassName, bodyClassName, footerClassName, overlayClassName, ariaLabel, ariaDescribedBy, adjustForKeyboard = true, scrollToFocusedInput = true, }, ref) => {
|
|
1453
|
+
const BottomSheet = React.forwardRef(({ isOpen, onClose, title, description, footer, children, variant = "default", showCloseButton = true, showDragHandle = true, closeOnOverlayClick = true, closeOnEscape = true, closeOnSwipeDown = true, maxHeight = "90vh", className, contentClassName, headerClassName, bodyClassName, footerClassName, overlayClassName, ariaLabel, ariaDescribedBy, adjustForKeyboard = true, scrollToFocusedInput = true, stickyContent, stickyContentClassName, }, ref) => {
|
|
1454
1454
|
const sheetRef = React.useRef(null);
|
|
1455
1455
|
const contentRef = ref || sheetRef;
|
|
1456
1456
|
const bodyRef = React.useRef(null);
|
|
@@ -1587,11 +1587,24 @@ const BottomSheet = React.forwardRef(({ isOpen, onClose, title, description, foo
|
|
|
1587
1587
|
}
|
|
1588
1588
|
};
|
|
1589
1589
|
}, [isOpen]);
|
|
1590
|
-
// Reset closing state when opening
|
|
1590
|
+
// Reset closing state and scroll position when opening
|
|
1591
1591
|
React.useEffect(() => {
|
|
1592
1592
|
if (isOpen) {
|
|
1593
1593
|
setIsClosing(false);
|
|
1594
1594
|
setKeyboardHeight(0);
|
|
1595
|
+
// Reset scroll to top when opening - use multiple timeouts for iOS reliability
|
|
1596
|
+
const resetScroll = () => {
|
|
1597
|
+
if (bodyRef.current) {
|
|
1598
|
+
bodyRef.current.scrollTop = 0;
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
// Immediate reset
|
|
1602
|
+
resetScroll();
|
|
1603
|
+
// After animation starts
|
|
1604
|
+
requestAnimationFrame(resetScroll);
|
|
1605
|
+
// After animation completes
|
|
1606
|
+
const timer = setTimeout(resetScroll, 350);
|
|
1607
|
+
return () => clearTimeout(timer);
|
|
1595
1608
|
}
|
|
1596
1609
|
}, [isOpen]);
|
|
1597
1610
|
// Scroll focused input into view when keyboard opens
|
|
@@ -1629,16 +1642,25 @@ const BottomSheet = React.forwardRef(({ isOpen, onClose, title, description, foo
|
|
|
1629
1642
|
return () => document.removeEventListener("focusin", handleFocusIn);
|
|
1630
1643
|
}, [isOpen, scrollToFocusedInput, adjustForKeyboard, viewportHeight, keyboardHeight]);
|
|
1631
1644
|
// Reset scroll to top when content changes (for filtered results)
|
|
1645
|
+
// This ensures empty states and filtered results are always visible
|
|
1632
1646
|
React.useEffect(() => {
|
|
1633
1647
|
if (!isOpen || !bodyRef.current)
|
|
1634
1648
|
return;
|
|
1635
1649
|
// Use requestAnimationFrame to ensure DOM has updated
|
|
1636
1650
|
requestAnimationFrame(() => {
|
|
1637
1651
|
if (bodyRef.current) {
|
|
1652
|
+
// Always scroll to top to show content (including empty states)
|
|
1638
1653
|
bodyRef.current.scrollTop = 0;
|
|
1654
|
+
// If keyboard is open, ensure content is in visible area
|
|
1655
|
+
if (keyboardHeight > 0) {
|
|
1656
|
+
// Force a re-render of the scroll position
|
|
1657
|
+
bodyRef.current.style.scrollBehavior = 'auto';
|
|
1658
|
+
bodyRef.current.scrollTop = 0;
|
|
1659
|
+
bodyRef.current.style.scrollBehavior = '';
|
|
1660
|
+
}
|
|
1639
1661
|
}
|
|
1640
1662
|
});
|
|
1641
|
-
}, [children, isOpen]);
|
|
1663
|
+
}, [children, isOpen, keyboardHeight]);
|
|
1642
1664
|
// Handle close with animation
|
|
1643
1665
|
const handleClose = React.useCallback(() => {
|
|
1644
1666
|
if (!onClose)
|
|
@@ -1737,23 +1759,30 @@ const BottomSheet = React.forwardRef(({ isOpen, onClose, title, description, foo
|
|
|
1737
1759
|
return null;
|
|
1738
1760
|
const hasHeader = title || description;
|
|
1739
1761
|
// Calculate dynamic max height based on keyboard
|
|
1762
|
+
// When keyboard is open, limit height to fit in visible viewport above keyboard
|
|
1740
1763
|
const dynamicMaxHeight = keyboardHeight > 0
|
|
1741
|
-
?
|
|
1764
|
+
? `${Math.min(viewportHeight - 20, viewportHeight * 0.9)}px`
|
|
1742
1765
|
: maxHeight;
|
|
1743
|
-
return (jsxs("div", { className: cn("fixed
|
|
1744
|
-
//
|
|
1745
|
-
|
|
1746
|
-
//
|
|
1766
|
+
return (jsxs("div", { className: cn("fixed z-9999 flex items-end justify-center",
|
|
1767
|
+
// Always position at bottom, container height adjusts for keyboard
|
|
1768
|
+
className), style: {
|
|
1769
|
+
// Position container to fill visible viewport (above keyboard)
|
|
1770
|
+
top: 0,
|
|
1771
|
+
left: 0,
|
|
1772
|
+
right: 0,
|
|
1773
|
+
// Use visual viewport height when keyboard is open to stay above keyboard
|
|
1747
1774
|
height: keyboardHeight > 0 ? `${viewportHeight}px` : "100%",
|
|
1775
|
+
// On iOS, also set bottom to ensure proper positioning
|
|
1776
|
+
...(keyboardHeight === 0 && { bottom: 0 }),
|
|
1748
1777
|
}, role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsx("div", { ref: overlayRef, className: cn("absolute inset-0 z-0 bg-black/50 backdrop-blur-sm transition-opacity duration-300", isClosing ? "opacity-0" : "opacity-100", overlayClassName), onClick: handleOverlayClick, "aria-hidden": "true" }), jsxs("div", { ref: contentRef, className: cn("relative z-10 w-full transition-transform duration-300", "flex flex-col overflow-hidden", variant === "default" && "bg-white rounded-t-2xl shadow-xl", isClosing ? "animate-slide-out-bottom" : "animate-slide-in-bottom",
|
|
1749
|
-
//
|
|
1750
|
-
|
|
1778
|
+
// Always position at bottom of container
|
|
1779
|
+
"mt-auto", contentClassName), style: {
|
|
1751
1780
|
maxHeight: dynamicMaxHeight,
|
|
1752
1781
|
// Add safe area padding at the bottom for devices with home indicator
|
|
1753
1782
|
paddingBottom: footer ? 0 : `max(env(safe-area-inset-bottom, 0px), ${safeAreaBottom}px)`,
|
|
1754
|
-
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [showDragHandle && variant === "default" && (jsx("div", { ref: dragHandleRef, className: "flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none", children: jsx("div", { className: "w-10 h-1 bg-neutral-300 rounded-full" }) })), hasHeader && (jsxs("div", { className: cn("flex items-start justify-between gap-4 shrink-0", variant === "default" && "px-6", variant === "default" && !showDragHandle && "pt-6", variant === "default" && showDragHandle && "pt-2", variant === "default" && !description && "pb-4", variant === "default" && description && "pb-2", headerClassName), children: [jsxs("div", { className: "flex-1 min-w-0", 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: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsx("div", { className: cn("absolute z-10", variant === "default" && "top-4 right-4"), children: jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet" }) })), jsx("div", { ref: bodyRef, className: cn("flex-1 overflow-y-auto overscroll-contain",
|
|
1783
|
+
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [showDragHandle && variant === "default" && (jsx("div", { ref: dragHandleRef, className: "flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none", children: jsx("div", { className: "w-10 h-1 bg-neutral-300 rounded-full" }) })), hasHeader && (jsxs("div", { className: cn("flex items-start justify-between gap-4 shrink-0", variant === "default" && "px-6", variant === "default" && !showDragHandle && "pt-6", variant === "default" && showDragHandle && "pt-2", variant === "default" && !description && "pb-4", variant === "default" && description && "pb-2", headerClassName), children: [jsxs("div", { className: "flex-1 min-w-0", 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: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsx("div", { className: cn("absolute z-10", variant === "default" && "top-4 right-4"), children: jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet" }) })), stickyContent && (jsx("div", { className: cn("shrink-0", variant === "default" && "px-6 pb-4", variant === "default" && !hasHeader && !showDragHandle && "pt-4", variant === "default" && !hasHeader && showDragHandle && "pt-2", stickyContentClassName), children: stickyContent })), jsx("div", { ref: bodyRef, className: cn("flex-1 min-h-0 overflow-y-auto overscroll-contain",
|
|
1755
1784
|
// Smooth scrolling and momentum scroll for iOS
|
|
1756
|
-
"-webkit-overflow-scrolling-touch", variant === "default" && "px-6", variant === "default" && hasHeader && "py-4", variant === "default" && !hasHeader && !showDragHandle && "pt-6 pb-4", variant === "default" && !hasHeader && showDragHandle && "pt-2 pb-4", variant === "default" && !footer && "pb-6", bodyClassName), children: children }), footer && (jsxs("div", { className: "flex flex-col shrink-0", style: {
|
|
1785
|
+
"-webkit-overflow-scrolling-touch", variant === "default" && "px-6", variant === "default" && hasHeader && !stickyContent && "py-4", variant === "default" && hasHeader && stickyContent && "pb-4", variant === "default" && !hasHeader && !showDragHandle && !stickyContent && "pt-6 pb-4", variant === "default" && !hasHeader && showDragHandle && !stickyContent && "pt-2 pb-4", variant === "default" && stickyContent && "pt-0 pb-4", variant === "default" && !footer && "pb-6", bodyClassName), children: children }), footer && (jsxs("div", { className: "flex flex-col shrink-0", style: {
|
|
1757
1786
|
// Add safe area padding to footer
|
|
1758
1787
|
paddingBottom: `max(env(safe-area-inset-bottom, 0px), ${safeAreaBottom}px)`,
|
|
1759
1788
|
}, children: [variant === "default" && (jsx(Divider, { thickness: "thin", variant: "muted" })), jsx("div", { className: cn("flex items-center justify-end gap-3", variant === "default" && "px-6 py-4", footerClassName), children: footer })] }))] })] }));
|
|
@@ -4864,11 +4893,16 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
4864
4893
|
});
|
|
4865
4894
|
const showDropdown = isOpen && searchValue.length >= minSearchLength;
|
|
4866
4895
|
// Render dropdown menu content
|
|
4867
|
-
const renderDropdownContent = () => (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
|
|
4896
|
+
const renderDropdownContent = () => (jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading: isMobile ? undefined : 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
|
|
4868
4897
|
? true
|
|
4869
|
-
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"),
|
|
4898
|
+
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"),
|
|
4899
|
+
// On mobile, let BottomSheet body handle scrolling; on desktop use calculated maxHeight
|
|
4900
|
+
maxHeight: isMobile ? "none" : `${position.maxHeight}px`, unstyled: isMobile }));
|
|
4901
|
+
// Search field component for mobile BottomSheet
|
|
4902
|
+
// Note: No autoFocus - keyboard only appears when user taps the field
|
|
4903
|
+
const mobileSearchField = (jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", ...textFieldProps }));
|
|
4870
4904
|
// Mobile: BottomSheet, Desktop: Regular Dropdown
|
|
4871
|
-
const dropdownMenu = showDropdown && (isMobile ? (
|
|
4905
|
+
const dropdownMenu = showDropdown && (isMobile ? (jsx(BottomSheet, { isOpen: isOpen, onClose: () => setIsOpen(false), title: sectionHeading, variant: "default", showDragHandle: true, closeOnOverlayClick: true, closeOnEscape: true, closeOnSwipeDown: true, adjustForKeyboard: true, scrollToFocusedInput: true, maxHeight: "85vh", stickyContent: mobileSearchField, children: renderDropdownContent() })) : (jsx("div", { ref: menuRef, style: {
|
|
4872
4906
|
position: "fixed",
|
|
4873
4907
|
...(position.top !== undefined && { top: `${position.top}px` }),
|
|
4874
4908
|
...(position.bottom !== undefined && {
|