infinity-ui-elements 1.14.5 → 1.14.7
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 +0 -19
- package/dist/components/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +23 -346
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +23 -346
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1431,102 +1431,12 @@ const Divider = React__namespace.forwardRef(({ className, orientation = "horizon
|
|
|
1431
1431
|
});
|
|
1432
1432
|
Divider.displayName = "Divider";
|
|
1433
1433
|
|
|
1434
|
-
|
|
1435
|
-
const isIOS = () => {
|
|
1436
|
-
if (typeof window === "undefined" || typeof navigator === "undefined")
|
|
1437
|
-
return false;
|
|
1438
|
-
return /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
1439
|
-
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
1440
|
-
};
|
|
1441
|
-
// Detect Android
|
|
1442
|
-
const isAndroid = () => {
|
|
1443
|
-
if (typeof navigator === "undefined")
|
|
1444
|
-
return false;
|
|
1445
|
-
return /Android/.test(navigator.userAgent);
|
|
1446
|
-
};
|
|
1447
|
-
// Get visual viewport height (accounts for keyboard)
|
|
1448
|
-
const getVisualViewportHeight = () => {
|
|
1449
|
-
if (typeof window === "undefined")
|
|
1450
|
-
return 0;
|
|
1451
|
-
// Use visualViewport API if available (better keyboard detection)
|
|
1452
|
-
if (window.visualViewport) {
|
|
1453
|
-
return window.visualViewport.height;
|
|
1454
|
-
}
|
|
1455
|
-
return window.innerHeight;
|
|
1456
|
-
};
|
|
1457
|
-
// Get safe area inset bottom (for devices with home indicator)
|
|
1458
|
-
const getSafeAreaInsetBottom = () => {
|
|
1459
|
-
if (typeof window === "undefined" || typeof document === "undefined")
|
|
1460
|
-
return 0;
|
|
1461
|
-
const root = document.documentElement;
|
|
1462
|
-
const safeAreaInset = getComputedStyle(root).getPropertyValue("--safe-area-inset-bottom");
|
|
1463
|
-
if (safeAreaInset) {
|
|
1464
|
-
return parseInt(safeAreaInset, 10) || 0;
|
|
1465
|
-
}
|
|
1466
|
-
// Fallback: try to read env() value
|
|
1467
|
-
const testDiv = document.createElement("div");
|
|
1468
|
-
testDiv.style.paddingBottom = "env(safe-area-inset-bottom, 0px)";
|
|
1469
|
-
document.body.appendChild(testDiv);
|
|
1470
|
-
const paddingBottom = parseInt(getComputedStyle(testDiv).paddingBottom, 10) || 0;
|
|
1471
|
-
document.body.removeChild(testDiv);
|
|
1472
|
-
return paddingBottom;
|
|
1473
|
-
};
|
|
1474
|
-
const BottomSheet = React__namespace.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) => {
|
|
1434
|
+
const BottomSheet = React__namespace.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, }, ref) => {
|
|
1475
1435
|
const sheetRef = React__namespace.useRef(null);
|
|
1476
1436
|
const contentRef = ref || sheetRef;
|
|
1477
|
-
const bodyRef = React__namespace.useRef(null);
|
|
1478
|
-
const dragHandleRef = React__namespace.useRef(null);
|
|
1479
|
-
const overlayRef = React__namespace.useRef(null);
|
|
1480
1437
|
const [isClosing, setIsClosing] = React__namespace.useState(false);
|
|
1481
1438
|
const [touchStart, setTouchStart] = React__namespace.useState(null);
|
|
1482
1439
|
const [touchEnd, setTouchEnd] = React__namespace.useState(null);
|
|
1483
|
-
const [touchStartX, setTouchStartX] = React__namespace.useState(null);
|
|
1484
|
-
const [isSwipeGesture, setIsSwipeGesture] = React__namespace.useState(false);
|
|
1485
|
-
const [keyboardHeight, setKeyboardHeight] = React__namespace.useState(0);
|
|
1486
|
-
const [viewportHeight, setViewportHeight] = React__namespace.useState(typeof window !== "undefined" ? window.innerHeight : 0);
|
|
1487
|
-
const [safeAreaBottom, setSafeAreaBottom] = React__namespace.useState(0);
|
|
1488
|
-
// Track if user started dragging from the drag handle
|
|
1489
|
-
const isDraggingFromHandle = React__namespace.useRef(false);
|
|
1490
|
-
// Store original body styles for restoration
|
|
1491
|
-
const originalBodyStyles = React__namespace.useRef(null);
|
|
1492
|
-
// Track scroll position before locking
|
|
1493
|
-
const scrollPosition = React__namespace.useRef(0);
|
|
1494
|
-
// Initialize safe area inset
|
|
1495
|
-
React__namespace.useEffect(() => {
|
|
1496
|
-
setSafeAreaBottom(getSafeAreaInsetBottom());
|
|
1497
|
-
}, []);
|
|
1498
|
-
// Handle Visual Viewport API for keyboard detection
|
|
1499
|
-
React__namespace.useEffect(() => {
|
|
1500
|
-
if (!isOpen || !adjustForKeyboard)
|
|
1501
|
-
return;
|
|
1502
|
-
if (typeof window === "undefined")
|
|
1503
|
-
return;
|
|
1504
|
-
const updateViewport = () => {
|
|
1505
|
-
const newHeight = getVisualViewportHeight();
|
|
1506
|
-
const fullHeight = window.innerHeight;
|
|
1507
|
-
const newKeyboardHeight = Math.max(0, fullHeight - newHeight);
|
|
1508
|
-
setViewportHeight(newHeight);
|
|
1509
|
-
setKeyboardHeight(newKeyboardHeight);
|
|
1510
|
-
};
|
|
1511
|
-
// Use visualViewport API if available
|
|
1512
|
-
if (window.visualViewport) {
|
|
1513
|
-
window.visualViewport.addEventListener("resize", updateViewport);
|
|
1514
|
-
window.visualViewport.addEventListener("scroll", updateViewport);
|
|
1515
|
-
updateViewport();
|
|
1516
|
-
return () => {
|
|
1517
|
-
window.visualViewport?.removeEventListener("resize", updateViewport);
|
|
1518
|
-
window.visualViewport?.removeEventListener("scroll", updateViewport);
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
// Fallback for browsers without visualViewport API
|
|
1523
|
-
window.addEventListener("resize", updateViewport);
|
|
1524
|
-
updateViewport();
|
|
1525
|
-
return () => {
|
|
1526
|
-
window.removeEventListener("resize", updateViewport);
|
|
1527
|
-
};
|
|
1528
|
-
}
|
|
1529
|
-
}, [isOpen, adjustForKeyboard]);
|
|
1530
1440
|
// Handle escape key
|
|
1531
1441
|
React__namespace.useEffect(() => {
|
|
1532
1442
|
if (!isOpen || !closeOnEscape || !onClose)
|
|
@@ -1539,297 +1449,69 @@ const BottomSheet = React__namespace.forwardRef(({ isOpen, onClose, title, descr
|
|
|
1539
1449
|
document.addEventListener("keydown", handleEscape);
|
|
1540
1450
|
return () => document.removeEventListener("keydown", handleEscape);
|
|
1541
1451
|
}, [isOpen, closeOnEscape, onClose]);
|
|
1542
|
-
//
|
|
1452
|
+
// Prevent body scroll when bottom sheet is open
|
|
1543
1453
|
React__namespace.useEffect(() => {
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
originalBodyStyles.current = {
|
|
1551
|
-
overflow: document.body.style.overflow,
|
|
1552
|
-
position: document.body.style.position,
|
|
1553
|
-
top: document.body.style.top,
|
|
1554
|
-
left: document.body.style.left,
|
|
1555
|
-
right: document.body.style.right,
|
|
1556
|
-
width: document.body.style.width,
|
|
1557
|
-
height: document.body.style.height,
|
|
1558
|
-
};
|
|
1559
|
-
// Apply scroll lock
|
|
1560
|
-
document.body.style.overflow = "hidden";
|
|
1561
|
-
// iOS requires position fixed to prevent background scroll
|
|
1562
|
-
if (iOS) {
|
|
1563
|
-
document.body.style.position = "fixed";
|
|
1564
|
-
document.body.style.top = `-${scrollPosition.current}px`;
|
|
1565
|
-
document.body.style.left = "0";
|
|
1566
|
-
document.body.style.right = "0";
|
|
1567
|
-
document.body.style.width = "100%";
|
|
1568
|
-
}
|
|
1569
|
-
// Track touch position for scroll boundary detection
|
|
1570
|
-
let lastTouchY = 0;
|
|
1571
|
-
const handleTouchStartForScroll = (e) => {
|
|
1572
|
-
lastTouchY = e.touches[0].clientY;
|
|
1573
|
-
};
|
|
1574
|
-
// Prevent touchmove on overlay to stop iOS rubber-banding
|
|
1575
|
-
// But always allow scrolling inside body content
|
|
1576
|
-
const preventTouchMove = (e) => {
|
|
1577
|
-
const target = e.target;
|
|
1578
|
-
const currentTouchY = e.touches[0].clientY;
|
|
1579
|
-
const touchDelta = currentTouchY - lastTouchY;
|
|
1580
|
-
lastTouchY = currentTouchY;
|
|
1581
|
-
// Always allow scrolling inside the body content
|
|
1582
|
-
if (bodyRef.current?.contains(target)) {
|
|
1583
|
-
const scrollTop = bodyRef.current.scrollTop;
|
|
1584
|
-
const scrollHeight = bodyRef.current.scrollHeight;
|
|
1585
|
-
const clientHeight = bodyRef.current.clientHeight;
|
|
1586
|
-
const canScroll = scrollHeight > clientHeight;
|
|
1587
|
-
// If content is scrollable, allow normal scrolling
|
|
1588
|
-
if (canScroll) {
|
|
1589
|
-
const isAtTop = scrollTop <= 0;
|
|
1590
|
-
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
1591
|
-
const isScrollingDown = touchDelta > 0; // finger moving down = scrolling up
|
|
1592
|
-
const isScrollingUp = touchDelta < 0; // finger moving up = scrolling down
|
|
1593
|
-
// Only prevent at boundaries when trying to scroll beyond
|
|
1594
|
-
if ((isAtTop && isScrollingDown) || (isAtBottom && isScrollingUp)) {
|
|
1595
|
-
// At boundary, prevent overscroll but allow swipe-to-close from drag handle
|
|
1596
|
-
if (isDraggingFromHandle.current) {
|
|
1597
|
-
return; // Allow swipe gesture
|
|
1598
|
-
}
|
|
1599
|
-
e.preventDefault();
|
|
1600
|
-
}
|
|
1601
|
-
// Otherwise, allow normal scrolling
|
|
1602
|
-
return;
|
|
1603
|
-
}
|
|
1604
|
-
// Content not scrollable, allow touch events to pass through
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
|
-
// Also allow scrolling in sticky content area
|
|
1608
|
-
if (target.closest('[data-sticky-content]')) {
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
// Prevent scroll on overlay and other areas (to prevent background scroll)
|
|
1612
|
-
e.preventDefault();
|
|
1613
|
-
};
|
|
1614
|
-
document.addEventListener("touchstart", handleTouchStartForScroll, { passive: true });
|
|
1615
|
-
document.addEventListener("touchmove", preventTouchMove, { passive: false });
|
|
1454
|
+
if (isOpen) {
|
|
1455
|
+
document.body.style.overflow = "hidden";
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
document.body.style.overflow = "";
|
|
1459
|
+
}
|
|
1616
1460
|
return () => {
|
|
1617
|
-
document.
|
|
1618
|
-
document.removeEventListener("touchmove", preventTouchMove);
|
|
1619
|
-
// Restore original styles
|
|
1620
|
-
if (originalBodyStyles.current) {
|
|
1621
|
-
document.body.style.overflow = originalBodyStyles.current.overflow;
|
|
1622
|
-
document.body.style.position = originalBodyStyles.current.position;
|
|
1623
|
-
document.body.style.top = originalBodyStyles.current.top;
|
|
1624
|
-
document.body.style.left = originalBodyStyles.current.left;
|
|
1625
|
-
document.body.style.right = originalBodyStyles.current.right;
|
|
1626
|
-
document.body.style.width = originalBodyStyles.current.width;
|
|
1627
|
-
}
|
|
1628
|
-
// Restore scroll position for iOS
|
|
1629
|
-
if (iOS) {
|
|
1630
|
-
window.scrollTo(0, scrollPosition.current);
|
|
1631
|
-
}
|
|
1461
|
+
document.body.style.overflow = "";
|
|
1632
1462
|
};
|
|
1633
1463
|
}, [isOpen]);
|
|
1634
|
-
// Reset closing state
|
|
1464
|
+
// Reset closing state when opening
|
|
1635
1465
|
React__namespace.useEffect(() => {
|
|
1636
1466
|
if (isOpen) {
|
|
1637
1467
|
setIsClosing(false);
|
|
1638
|
-
setKeyboardHeight(0);
|
|
1639
|
-
// Reset scroll to top when opening - use multiple timeouts for iOS reliability
|
|
1640
|
-
const resetScroll = () => {
|
|
1641
|
-
if (bodyRef.current) {
|
|
1642
|
-
bodyRef.current.scrollTop = 0;
|
|
1643
|
-
}
|
|
1644
|
-
};
|
|
1645
|
-
// Immediate reset
|
|
1646
|
-
resetScroll();
|
|
1647
|
-
// After animation starts
|
|
1648
|
-
requestAnimationFrame(resetScroll);
|
|
1649
|
-
// After animation completes
|
|
1650
|
-
const timer = setTimeout(resetScroll, 350);
|
|
1651
|
-
return () => clearTimeout(timer);
|
|
1652
1468
|
}
|
|
1653
1469
|
}, [isOpen]);
|
|
1654
|
-
// Scroll focused input into view when keyboard opens
|
|
1655
|
-
React__namespace.useEffect(() => {
|
|
1656
|
-
if (!isOpen || !scrollToFocusedInput || !adjustForKeyboard)
|
|
1657
|
-
return;
|
|
1658
|
-
const handleFocusIn = (e) => {
|
|
1659
|
-
const target = e.target;
|
|
1660
|
-
if (!target || !bodyRef.current)
|
|
1661
|
-
return;
|
|
1662
|
-
// Check if target is an input/textarea inside the body
|
|
1663
|
-
if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") &&
|
|
1664
|
-
bodyRef.current.contains(target)) {
|
|
1665
|
-
// Wait for keyboard to open
|
|
1666
|
-
setTimeout(() => {
|
|
1667
|
-
// Scroll the input into view within the body container
|
|
1668
|
-
const targetRect = target.getBoundingClientRect();
|
|
1669
|
-
const bodyRect = bodyRef.current?.getBoundingClientRect();
|
|
1670
|
-
if (!bodyRect)
|
|
1671
|
-
return;
|
|
1672
|
-
// Calculate if input is below visible area (considering keyboard)
|
|
1673
|
-
const visibleBottom = viewportHeight - keyboardHeight - 20; // 20px buffer
|
|
1674
|
-
if (targetRect.bottom > visibleBottom) {
|
|
1675
|
-
// Scroll to bring input into view
|
|
1676
|
-
const scrollAmount = targetRect.bottom - visibleBottom + 40;
|
|
1677
|
-
bodyRef.current?.scrollBy({
|
|
1678
|
-
top: scrollAmount,
|
|
1679
|
-
behavior: "smooth",
|
|
1680
|
-
});
|
|
1681
|
-
}
|
|
1682
|
-
}, 100);
|
|
1683
|
-
}
|
|
1684
|
-
};
|
|
1685
|
-
document.addEventListener("focusin", handleFocusIn);
|
|
1686
|
-
return () => document.removeEventListener("focusin", handleFocusIn);
|
|
1687
|
-
}, [isOpen, scrollToFocusedInput, adjustForKeyboard, viewportHeight, keyboardHeight]);
|
|
1688
|
-
// Reset scroll to top when content changes (for filtered results)
|
|
1689
|
-
// This ensures empty states and filtered results are always visible
|
|
1690
|
-
React__namespace.useEffect(() => {
|
|
1691
|
-
if (!isOpen || !bodyRef.current)
|
|
1692
|
-
return;
|
|
1693
|
-
// Use requestAnimationFrame to ensure DOM has updated
|
|
1694
|
-
requestAnimationFrame(() => {
|
|
1695
|
-
if (bodyRef.current) {
|
|
1696
|
-
// Always scroll to top to show content (including empty states)
|
|
1697
|
-
bodyRef.current.scrollTop = 0;
|
|
1698
|
-
// If keyboard is open, ensure content is in visible area
|
|
1699
|
-
if (keyboardHeight > 0) {
|
|
1700
|
-
// Force a re-render of the scroll position
|
|
1701
|
-
bodyRef.current.style.scrollBehavior = 'auto';
|
|
1702
|
-
bodyRef.current.scrollTop = 0;
|
|
1703
|
-
bodyRef.current.style.scrollBehavior = '';
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
});
|
|
1707
|
-
}, [children, isOpen, keyboardHeight]);
|
|
1708
1470
|
// Handle close with animation
|
|
1709
|
-
const handleClose =
|
|
1471
|
+
const handleClose = () => {
|
|
1710
1472
|
if (!onClose)
|
|
1711
1473
|
return;
|
|
1712
|
-
// Blur any focused input to dismiss keyboard
|
|
1713
|
-
if (document.activeElement instanceof HTMLElement) {
|
|
1714
|
-
document.activeElement.blur();
|
|
1715
|
-
}
|
|
1716
1474
|
setIsClosing(true);
|
|
1717
1475
|
// Wait for animation to complete before calling onClose
|
|
1718
1476
|
setTimeout(() => {
|
|
1719
1477
|
onClose();
|
|
1720
1478
|
setIsClosing(false);
|
|
1721
1479
|
}, 300); // Match animation duration
|
|
1722
|
-
}
|
|
1480
|
+
};
|
|
1723
1481
|
// Handle overlay click
|
|
1724
1482
|
const handleOverlayClick = (e) => {
|
|
1725
1483
|
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
1726
1484
|
handleClose();
|
|
1727
1485
|
}
|
|
1728
1486
|
};
|
|
1729
|
-
//
|
|
1487
|
+
// Handle touch events for swipe down
|
|
1730
1488
|
const handleTouchStart = (e) => {
|
|
1731
1489
|
if (!closeOnSwipeDown)
|
|
1732
1490
|
return;
|
|
1733
|
-
const touch = e.targetTouches[0];
|
|
1734
1491
|
setTouchEnd(null);
|
|
1735
|
-
setTouchStart(
|
|
1736
|
-
setTouchStartX(touch.clientX);
|
|
1737
|
-
setIsSwipeGesture(false);
|
|
1738
|
-
// Check if drag started from the drag handle
|
|
1739
|
-
isDraggingFromHandle.current = dragHandleRef.current?.contains(e.target) || false;
|
|
1492
|
+
setTouchStart(e.targetTouches[0].clientY);
|
|
1740
1493
|
};
|
|
1741
1494
|
const handleTouchMove = (e) => {
|
|
1742
|
-
if (!closeOnSwipeDown
|
|
1495
|
+
if (!closeOnSwipeDown)
|
|
1743
1496
|
return;
|
|
1744
|
-
|
|
1745
|
-
const currentY = touch.clientY;
|
|
1746
|
-
const currentX = touch.clientX;
|
|
1747
|
-
setTouchEnd(currentY);
|
|
1748
|
-
// Determine if this is a vertical swipe gesture (vs horizontal scroll)
|
|
1749
|
-
if (!isSwipeGesture && touchStartX !== null) {
|
|
1750
|
-
const deltaY = Math.abs(currentY - touchStart);
|
|
1751
|
-
const deltaX = Math.abs(currentX - touchStartX);
|
|
1752
|
-
// If vertical movement is greater, it's a swipe gesture
|
|
1753
|
-
if (deltaY > 10 || deltaX > 10) {
|
|
1754
|
-
setIsSwipeGesture(deltaY > deltaX);
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1497
|
+
setTouchEnd(e.targetTouches[0].clientY);
|
|
1757
1498
|
};
|
|
1758
1499
|
const handleTouchEnd = () => {
|
|
1759
|
-
if (!closeOnSwipeDown || touchStart
|
|
1760
|
-
resetTouchState();
|
|
1500
|
+
if (!closeOnSwipeDown || !touchStart || !touchEnd)
|
|
1761
1501
|
return;
|
|
1762
|
-
}
|
|
1763
1502
|
const distance = touchEnd - touchStart;
|
|
1764
|
-
const isSwipeDown = distance >
|
|
1765
|
-
|
|
1766
|
-
// 1. Swiped from drag handle, OR
|
|
1767
|
-
// 2. Body is scrolled to top and user swiped down
|
|
1768
|
-
const bodyScrolledToTop = (bodyRef.current?.scrollTop || 0) <= 0;
|
|
1769
|
-
const shouldClose = isSwipeDown && isSwipeGesture && (isDraggingFromHandle.current || bodyScrolledToTop);
|
|
1770
|
-
if (shouldClose) {
|
|
1503
|
+
const isSwipeDown = distance > 100; // Minimum swipe distance
|
|
1504
|
+
if (isSwipeDown) {
|
|
1771
1505
|
handleClose();
|
|
1772
1506
|
}
|
|
1773
|
-
resetTouchState();
|
|
1774
|
-
};
|
|
1775
|
-
const resetTouchState = () => {
|
|
1776
1507
|
setTouchStart(null);
|
|
1777
1508
|
setTouchEnd(null);
|
|
1778
|
-
setTouchStartX(null);
|
|
1779
|
-
setIsSwipeGesture(false);
|
|
1780
|
-
isDraggingFromHandle.current = false;
|
|
1781
1509
|
};
|
|
1782
|
-
// Handle Android back button
|
|
1783
|
-
React__namespace.useEffect(() => {
|
|
1784
|
-
if (!isOpen || !isAndroid())
|
|
1785
|
-
return;
|
|
1786
|
-
const handlePopState = (e) => {
|
|
1787
|
-
e.preventDefault();
|
|
1788
|
-
handleClose();
|
|
1789
|
-
};
|
|
1790
|
-
// Push a dummy state so back button can be caught
|
|
1791
|
-
window.history.pushState({ bottomSheet: true }, "");
|
|
1792
|
-
window.addEventListener("popstate", handlePopState);
|
|
1793
|
-
return () => {
|
|
1794
|
-
window.removeEventListener("popstate", handlePopState);
|
|
1795
|
-
// Clean up the dummy state if still present
|
|
1796
|
-
if (window.history.state?.bottomSheet) {
|
|
1797
|
-
window.history.back();
|
|
1798
|
-
}
|
|
1799
|
-
};
|
|
1800
|
-
}, [isOpen, handleClose]);
|
|
1801
1510
|
// Don't render if not open and not closing
|
|
1802
1511
|
if (!isOpen && !isClosing)
|
|
1803
1512
|
return null;
|
|
1804
1513
|
const hasHeader = title || description;
|
|
1805
|
-
|
|
1806
|
-
// When keyboard is open, limit height to fit in visible viewport above keyboard
|
|
1807
|
-
const dynamicMaxHeight = keyboardHeight > 0
|
|
1808
|
-
? `${Math.min(viewportHeight - 20, viewportHeight * 0.9)}px`
|
|
1809
|
-
: maxHeight;
|
|
1810
|
-
return (jsxRuntime.jsxs("div", { className: cn("fixed z-9999 flex items-end justify-center",
|
|
1811
|
-
// Always position at bottom, container height adjusts for keyboard
|
|
1812
|
-
className), style: {
|
|
1813
|
-
// Position container to fill visible viewport (above keyboard)
|
|
1814
|
-
top: 0,
|
|
1815
|
-
left: 0,
|
|
1816
|
-
right: 0,
|
|
1817
|
-
// Use visual viewport height when keyboard is open to stay above keyboard
|
|
1818
|
-
height: keyboardHeight > 0 ? `${viewportHeight}px` : "100%",
|
|
1819
|
-
// On iOS, also set bottom to ensure proper positioning
|
|
1820
|
-
...(keyboardHeight === 0 && { bottom: 0 }),
|
|
1821
|
-
}, role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsxRuntime.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" }), jsxRuntime.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",
|
|
1822
|
-
// Always position at bottom of container
|
|
1823
|
-
"mt-auto", contentClassName), style: {
|
|
1824
|
-
maxHeight: dynamicMaxHeight,
|
|
1825
|
-
// Add safe area padding at the bottom for devices with home indicator
|
|
1826
|
-
paddingBottom: footer ? 0 : `max(env(safe-area-inset-bottom, 0px), ${safeAreaBottom}px)`,
|
|
1827
|
-
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [showDragHandle && variant === "default" && (jsxRuntime.jsx("div", { ref: dragHandleRef, className: "flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none", children: jsxRuntime.jsx("div", { className: "w-10 h-1 bg-neutral-300 rounded-full" }) })), hasHeader && (jsxRuntime.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: [jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [title && (jsxRuntime.jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsxRuntime.jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && onClose && (jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsxRuntime.jsx("div", { className: cn("absolute z-10", variant === "default" && "top-4 right-4"), children: jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet" }) })), stickyContent && (jsxRuntime.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 })), jsxRuntime.jsx("div", { ref: bodyRef, className: cn("flex-1 min-h-0 overflow-y-auto overscroll-contain",
|
|
1828
|
-
// Smooth scrolling and momentum scroll for iOS
|
|
1829
|
-
"-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 && (jsxRuntime.jsxs("div", { className: "flex flex-col shrink-0", style: {
|
|
1830
|
-
// Add safe area padding to footer
|
|
1831
|
-
paddingBottom: `max(env(safe-area-inset-bottom, 0px), ${safeAreaBottom}px)`,
|
|
1832
|
-
}, children: [variant === "default" && (jsxRuntime.jsx(Divider, { thickness: "thin", variant: "muted" })), jsxRuntime.jsx("div", { className: cn("flex items-center justify-end gap-3", variant === "default" && "px-6 py-4", footerClassName), children: footer })] }))] })] }));
|
|
1514
|
+
return (jsxRuntime.jsxs("div", { className: cn("fixed inset-0 z-9999 flex items-end justify-center", className), role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsxRuntime.jsx("div", { 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" }), jsxRuntime.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", contentClassName), style: { maxHeight }, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [showDragHandle && variant === "default" && (jsxRuntime.jsx("div", { className: "flex justify-center pt-3 pb-2", children: jsxRuntime.jsx("div", { className: "w-10 h-1 bg-neutral-300 rounded-full" }) })), hasHeader && (jsxRuntime.jsxs("div", { className: cn("flex items-start justify-between gap-4", 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: [jsxRuntime.jsxs("div", { className: "flex-1", children: [title && (jsxRuntime.jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsxRuntime.jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && onClose && (jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsxRuntime.jsx("div", { className: cn("absolute z-10", variant === "default" && "top-4 right-4"), children: jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet" }) })), jsxRuntime.jsx("div", { className: cn("flex-1 overflow-y-auto", 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 && (jsxRuntime.jsxs("div", { className: "flex flex-col", children: [variant === "default" && (jsxRuntime.jsx(Divider, { thickness: "thin", variant: "muted" })), jsxRuntime.jsx("div", { className: cn("flex items-center justify-end gap-3", variant === "default" && "px-6 py-4", footerClassName), children: footer })] }))] })] }));
|
|
1833
1515
|
});
|
|
1834
1516
|
BottomSheet.displayName = "BottomSheet";
|
|
1835
1517
|
|
|
@@ -4937,16 +4619,11 @@ const SearchableDropdown = React__namespace.forwardRef(({ className, items = [],
|
|
|
4937
4619
|
});
|
|
4938
4620
|
const showDropdown = isOpen && searchValue.length >= minSearchLength;
|
|
4939
4621
|
// Render dropdown menu content
|
|
4940
|
-
const renderDropdownContent = () => (jsxRuntime.jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading:
|
|
4622
|
+
const renderDropdownContent = () => (jsxRuntime.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
|
|
4941
4623
|
? true
|
|
4942
|
-
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"),
|
|
4943
|
-
// On mobile, let BottomSheet body handle scrolling; on desktop use calculated maxHeight
|
|
4944
|
-
maxHeight: isMobile ? "none" : `${position.maxHeight}px`, unstyled: isMobile }));
|
|
4945
|
-
// Search field component for mobile BottomSheet
|
|
4946
|
-
// Note: No autoFocus - keyboard only appears when user taps the field
|
|
4947
|
-
const mobileSearchField = (jsxRuntime.jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", ...textFieldProps }));
|
|
4624
|
+
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"), maxHeight: `${position.maxHeight}px`, unstyled: isMobile }));
|
|
4948
4625
|
// Mobile: BottomSheet, Desktop: Regular Dropdown
|
|
4949
|
-
const dropdownMenu = showDropdown && (isMobile ? (jsxRuntime.
|
|
4626
|
+
const dropdownMenu = showDropdown && (isMobile ? (jsxRuntime.jsxs(BottomSheet, { isOpen: isOpen, onClose: () => setIsOpen(false), title: sectionHeading, variant: "default", showDragHandle: true, closeOnOverlayClick: true, closeOnEscape: true, closeOnSwipeDown: true, children: [jsxRuntime.jsx("div", { className: "mb-4", children: jsxRuntime.jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", ...textFieldProps }) }), renderDropdownContent()] })) : (jsxRuntime.jsx("div", { ref: menuRef, style: {
|
|
4950
4627
|
position: "fixed",
|
|
4951
4628
|
...(position.top !== undefined && { top: `${position.top}px` }),
|
|
4952
4629
|
...(position.bottom !== undefined && {
|