infinity-ui-elements 1.14.0 → 1.14.2
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 +10 -0
- 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 +286 -20
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +286 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1410,12 +1410,102 @@ const Divider = React.forwardRef(({ className, orientation = "horizontal", thick
|
|
|
1410
1410
|
});
|
|
1411
1411
|
Divider.displayName = "Divider";
|
|
1412
1412
|
|
|
1413
|
-
|
|
1413
|
+
// Detect iOS
|
|
1414
|
+
const isIOS = () => {
|
|
1415
|
+
if (typeof window === "undefined" || typeof navigator === "undefined")
|
|
1416
|
+
return false;
|
|
1417
|
+
return /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
1418
|
+
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
1419
|
+
};
|
|
1420
|
+
// Detect Android
|
|
1421
|
+
const isAndroid = () => {
|
|
1422
|
+
if (typeof navigator === "undefined")
|
|
1423
|
+
return false;
|
|
1424
|
+
return /Android/.test(navigator.userAgent);
|
|
1425
|
+
};
|
|
1426
|
+
// Get visual viewport height (accounts for keyboard)
|
|
1427
|
+
const getVisualViewportHeight = () => {
|
|
1428
|
+
if (typeof window === "undefined")
|
|
1429
|
+
return 0;
|
|
1430
|
+
// Use visualViewport API if available (better keyboard detection)
|
|
1431
|
+
if (window.visualViewport) {
|
|
1432
|
+
return window.visualViewport.height;
|
|
1433
|
+
}
|
|
1434
|
+
return window.innerHeight;
|
|
1435
|
+
};
|
|
1436
|
+
// Get safe area inset bottom (for devices with home indicator)
|
|
1437
|
+
const getSafeAreaInsetBottom = () => {
|
|
1438
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
1439
|
+
return 0;
|
|
1440
|
+
const root = document.documentElement;
|
|
1441
|
+
const safeAreaInset = getComputedStyle(root).getPropertyValue("--safe-area-inset-bottom");
|
|
1442
|
+
if (safeAreaInset) {
|
|
1443
|
+
return parseInt(safeAreaInset, 10) || 0;
|
|
1444
|
+
}
|
|
1445
|
+
// Fallback: try to read env() value
|
|
1446
|
+
const testDiv = document.createElement("div");
|
|
1447
|
+
testDiv.style.paddingBottom = "env(safe-area-inset-bottom, 0px)";
|
|
1448
|
+
document.body.appendChild(testDiv);
|
|
1449
|
+
const paddingBottom = parseInt(getComputedStyle(testDiv).paddingBottom, 10) || 0;
|
|
1450
|
+
document.body.removeChild(testDiv);
|
|
1451
|
+
return paddingBottom;
|
|
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) => {
|
|
1414
1454
|
const sheetRef = React.useRef(null);
|
|
1415
1455
|
const contentRef = ref || sheetRef;
|
|
1456
|
+
const bodyRef = React.useRef(null);
|
|
1457
|
+
const dragHandleRef = React.useRef(null);
|
|
1458
|
+
const overlayRef = React.useRef(null);
|
|
1416
1459
|
const [isClosing, setIsClosing] = React.useState(false);
|
|
1417
1460
|
const [touchStart, setTouchStart] = React.useState(null);
|
|
1418
1461
|
const [touchEnd, setTouchEnd] = React.useState(null);
|
|
1462
|
+
const [touchStartX, setTouchStartX] = React.useState(null);
|
|
1463
|
+
const [isSwipeGesture, setIsSwipeGesture] = React.useState(false);
|
|
1464
|
+
const [keyboardHeight, setKeyboardHeight] = React.useState(0);
|
|
1465
|
+
const [viewportHeight, setViewportHeight] = React.useState(typeof window !== "undefined" ? window.innerHeight : 0);
|
|
1466
|
+
const [safeAreaBottom, setSafeAreaBottom] = React.useState(0);
|
|
1467
|
+
// Track if user started dragging from the drag handle
|
|
1468
|
+
const isDraggingFromHandle = React.useRef(false);
|
|
1469
|
+
// Store original body styles for restoration
|
|
1470
|
+
const originalBodyStyles = React.useRef(null);
|
|
1471
|
+
// Track scroll position before locking
|
|
1472
|
+
const scrollPosition = React.useRef(0);
|
|
1473
|
+
// Initialize safe area inset
|
|
1474
|
+
React.useEffect(() => {
|
|
1475
|
+
setSafeAreaBottom(getSafeAreaInsetBottom());
|
|
1476
|
+
}, []);
|
|
1477
|
+
// Handle Visual Viewport API for keyboard detection
|
|
1478
|
+
React.useEffect(() => {
|
|
1479
|
+
if (!isOpen || !adjustForKeyboard)
|
|
1480
|
+
return;
|
|
1481
|
+
if (typeof window === "undefined")
|
|
1482
|
+
return;
|
|
1483
|
+
const updateViewport = () => {
|
|
1484
|
+
const newHeight = getVisualViewportHeight();
|
|
1485
|
+
const fullHeight = window.innerHeight;
|
|
1486
|
+
const newKeyboardHeight = Math.max(0, fullHeight - newHeight);
|
|
1487
|
+
setViewportHeight(newHeight);
|
|
1488
|
+
setKeyboardHeight(newKeyboardHeight);
|
|
1489
|
+
};
|
|
1490
|
+
// Use visualViewport API if available
|
|
1491
|
+
if (window.visualViewport) {
|
|
1492
|
+
window.visualViewport.addEventListener("resize", updateViewport);
|
|
1493
|
+
window.visualViewport.addEventListener("scroll", updateViewport);
|
|
1494
|
+
updateViewport();
|
|
1495
|
+
return () => {
|
|
1496
|
+
window.visualViewport?.removeEventListener("resize", updateViewport);
|
|
1497
|
+
window.visualViewport?.removeEventListener("scroll", updateViewport);
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
// Fallback for browsers without visualViewport API
|
|
1502
|
+
window.addEventListener("resize", updateViewport);
|
|
1503
|
+
updateViewport();
|
|
1504
|
+
return () => {
|
|
1505
|
+
window.removeEventListener("resize", updateViewport);
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
}, [isOpen, adjustForKeyboard]);
|
|
1419
1509
|
// Handle escape key
|
|
1420
1510
|
React.useEffect(() => {
|
|
1421
1511
|
if (!isOpen || !closeOnEscape || !onClose)
|
|
@@ -1428,69 +1518,245 @@ const BottomSheet = React.forwardRef(({ isOpen, onClose, title, description, foo
|
|
|
1428
1518
|
document.addEventListener("keydown", handleEscape);
|
|
1429
1519
|
return () => document.removeEventListener("keydown", handleEscape);
|
|
1430
1520
|
}, [isOpen, closeOnEscape, onClose]);
|
|
1431
|
-
//
|
|
1521
|
+
// Robust body scroll lock for iOS and Android
|
|
1432
1522
|
React.useEffect(() => {
|
|
1433
|
-
if (isOpen)
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1523
|
+
if (!isOpen)
|
|
1524
|
+
return;
|
|
1525
|
+
const iOS = isIOS();
|
|
1526
|
+
// Store current scroll position
|
|
1527
|
+
scrollPosition.current = window.pageYOffset || document.documentElement.scrollTop;
|
|
1528
|
+
// Store original styles
|
|
1529
|
+
originalBodyStyles.current = {
|
|
1530
|
+
overflow: document.body.style.overflow,
|
|
1531
|
+
position: document.body.style.position,
|
|
1532
|
+
top: document.body.style.top,
|
|
1533
|
+
left: document.body.style.left,
|
|
1534
|
+
right: document.body.style.right,
|
|
1535
|
+
width: document.body.style.width,
|
|
1536
|
+
height: document.body.style.height,
|
|
1537
|
+
};
|
|
1538
|
+
// Apply scroll lock
|
|
1539
|
+
document.body.style.overflow = "hidden";
|
|
1540
|
+
// iOS requires position fixed to prevent background scroll
|
|
1541
|
+
if (iOS) {
|
|
1542
|
+
document.body.style.position = "fixed";
|
|
1543
|
+
document.body.style.top = `-${scrollPosition.current}px`;
|
|
1544
|
+
document.body.style.left = "0";
|
|
1545
|
+
document.body.style.right = "0";
|
|
1546
|
+
document.body.style.width = "100%";
|
|
1547
|
+
}
|
|
1548
|
+
// Prevent touchmove on overlay to stop iOS rubber-banding
|
|
1549
|
+
const preventTouchMove = (e) => {
|
|
1550
|
+
const target = e.target;
|
|
1551
|
+
// Allow scrolling inside the body content
|
|
1552
|
+
if (bodyRef.current?.contains(target)) {
|
|
1553
|
+
const scrollTop = bodyRef.current.scrollTop;
|
|
1554
|
+
const scrollHeight = bodyRef.current.scrollHeight;
|
|
1555
|
+
const clientHeight = bodyRef.current.clientHeight;
|
|
1556
|
+
// Check if at scroll boundaries
|
|
1557
|
+
const isAtTop = scrollTop <= 0;
|
|
1558
|
+
const isAtBottom = scrollTop + clientHeight >= scrollHeight;
|
|
1559
|
+
// Only prevent default if at boundaries and trying to scroll further
|
|
1560
|
+
if ((isAtTop && e.touches[0].clientY > (touchStart || 0)) ||
|
|
1561
|
+
(isAtBottom && e.touches[0].clientY < (touchStart || 0))) {
|
|
1562
|
+
// Allow the swipe-to-close gesture
|
|
1563
|
+
if (!isDraggingFromHandle.current) {
|
|
1564
|
+
e.preventDefault();
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
// Prevent scroll on overlay and other areas
|
|
1570
|
+
e.preventDefault();
|
|
1571
|
+
};
|
|
1572
|
+
document.addEventListener("touchmove", preventTouchMove, { passive: false });
|
|
1439
1573
|
return () => {
|
|
1440
|
-
document.
|
|
1574
|
+
document.removeEventListener("touchmove", preventTouchMove);
|
|
1575
|
+
// Restore original styles
|
|
1576
|
+
if (originalBodyStyles.current) {
|
|
1577
|
+
document.body.style.overflow = originalBodyStyles.current.overflow;
|
|
1578
|
+
document.body.style.position = originalBodyStyles.current.position;
|
|
1579
|
+
document.body.style.top = originalBodyStyles.current.top;
|
|
1580
|
+
document.body.style.left = originalBodyStyles.current.left;
|
|
1581
|
+
document.body.style.right = originalBodyStyles.current.right;
|
|
1582
|
+
document.body.style.width = originalBodyStyles.current.width;
|
|
1583
|
+
}
|
|
1584
|
+
// Restore scroll position for iOS
|
|
1585
|
+
if (iOS) {
|
|
1586
|
+
window.scrollTo(0, scrollPosition.current);
|
|
1587
|
+
}
|
|
1441
1588
|
};
|
|
1442
1589
|
}, [isOpen]);
|
|
1443
1590
|
// Reset closing state when opening
|
|
1444
1591
|
React.useEffect(() => {
|
|
1445
1592
|
if (isOpen) {
|
|
1446
1593
|
setIsClosing(false);
|
|
1594
|
+
setKeyboardHeight(0);
|
|
1447
1595
|
}
|
|
1448
1596
|
}, [isOpen]);
|
|
1597
|
+
// Scroll focused input into view when keyboard opens
|
|
1598
|
+
React.useEffect(() => {
|
|
1599
|
+
if (!isOpen || !scrollToFocusedInput || !adjustForKeyboard)
|
|
1600
|
+
return;
|
|
1601
|
+
const handleFocusIn = (e) => {
|
|
1602
|
+
const target = e.target;
|
|
1603
|
+
if (!target || !bodyRef.current)
|
|
1604
|
+
return;
|
|
1605
|
+
// Check if target is an input/textarea inside the body
|
|
1606
|
+
if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") &&
|
|
1607
|
+
bodyRef.current.contains(target)) {
|
|
1608
|
+
// Wait for keyboard to open
|
|
1609
|
+
setTimeout(() => {
|
|
1610
|
+
// Scroll the input into view within the body container
|
|
1611
|
+
const targetRect = target.getBoundingClientRect();
|
|
1612
|
+
const bodyRect = bodyRef.current?.getBoundingClientRect();
|
|
1613
|
+
if (!bodyRect)
|
|
1614
|
+
return;
|
|
1615
|
+
// Calculate if input is below visible area (considering keyboard)
|
|
1616
|
+
const visibleBottom = viewportHeight - keyboardHeight - 20; // 20px buffer
|
|
1617
|
+
if (targetRect.bottom > visibleBottom) {
|
|
1618
|
+
// Scroll to bring input into view
|
|
1619
|
+
const scrollAmount = targetRect.bottom - visibleBottom + 40;
|
|
1620
|
+
bodyRef.current?.scrollBy({
|
|
1621
|
+
top: scrollAmount,
|
|
1622
|
+
behavior: "smooth",
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
}, 100);
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
document.addEventListener("focusin", handleFocusIn);
|
|
1629
|
+
return () => document.removeEventListener("focusin", handleFocusIn);
|
|
1630
|
+
}, [isOpen, scrollToFocusedInput, adjustForKeyboard, viewportHeight, keyboardHeight]);
|
|
1631
|
+
// Reset scroll to top when content changes (for filtered results)
|
|
1632
|
+
React.useEffect(() => {
|
|
1633
|
+
if (!isOpen || !bodyRef.current)
|
|
1634
|
+
return;
|
|
1635
|
+
// Use requestAnimationFrame to ensure DOM has updated
|
|
1636
|
+
requestAnimationFrame(() => {
|
|
1637
|
+
if (bodyRef.current) {
|
|
1638
|
+
bodyRef.current.scrollTop = 0;
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
}, [children, isOpen]);
|
|
1449
1642
|
// Handle close with animation
|
|
1450
|
-
const handleClose = () => {
|
|
1643
|
+
const handleClose = React.useCallback(() => {
|
|
1451
1644
|
if (!onClose)
|
|
1452
1645
|
return;
|
|
1646
|
+
// Blur any focused input to dismiss keyboard
|
|
1647
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
1648
|
+
document.activeElement.blur();
|
|
1649
|
+
}
|
|
1453
1650
|
setIsClosing(true);
|
|
1454
1651
|
// Wait for animation to complete before calling onClose
|
|
1455
1652
|
setTimeout(() => {
|
|
1456
1653
|
onClose();
|
|
1457
1654
|
setIsClosing(false);
|
|
1458
1655
|
}, 300); // Match animation duration
|
|
1459
|
-
};
|
|
1656
|
+
}, [onClose]);
|
|
1460
1657
|
// Handle overlay click
|
|
1461
1658
|
const handleOverlayClick = (e) => {
|
|
1462
1659
|
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
1463
1660
|
handleClose();
|
|
1464
1661
|
}
|
|
1465
1662
|
};
|
|
1466
|
-
//
|
|
1663
|
+
// Touch handling for swipe-to-close (only from drag handle or when scrolled to top)
|
|
1467
1664
|
const handleTouchStart = (e) => {
|
|
1468
1665
|
if (!closeOnSwipeDown)
|
|
1469
1666
|
return;
|
|
1667
|
+
const touch = e.targetTouches[0];
|
|
1470
1668
|
setTouchEnd(null);
|
|
1471
|
-
setTouchStart(
|
|
1669
|
+
setTouchStart(touch.clientY);
|
|
1670
|
+
setTouchStartX(touch.clientX);
|
|
1671
|
+
setIsSwipeGesture(false);
|
|
1672
|
+
// Check if drag started from the drag handle
|
|
1673
|
+
isDraggingFromHandle.current = dragHandleRef.current?.contains(e.target) || false;
|
|
1472
1674
|
};
|
|
1473
1675
|
const handleTouchMove = (e) => {
|
|
1474
|
-
if (!closeOnSwipeDown)
|
|
1676
|
+
if (!closeOnSwipeDown || touchStart === null)
|
|
1475
1677
|
return;
|
|
1476
|
-
|
|
1678
|
+
const touch = e.targetTouches[0];
|
|
1679
|
+
const currentY = touch.clientY;
|
|
1680
|
+
const currentX = touch.clientX;
|
|
1681
|
+
setTouchEnd(currentY);
|
|
1682
|
+
// Determine if this is a vertical swipe gesture (vs horizontal scroll)
|
|
1683
|
+
if (!isSwipeGesture && touchStartX !== null) {
|
|
1684
|
+
const deltaY = Math.abs(currentY - touchStart);
|
|
1685
|
+
const deltaX = Math.abs(currentX - touchStartX);
|
|
1686
|
+
// If vertical movement is greater, it's a swipe gesture
|
|
1687
|
+
if (deltaY > 10 || deltaX > 10) {
|
|
1688
|
+
setIsSwipeGesture(deltaY > deltaX);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1477
1691
|
};
|
|
1478
1692
|
const handleTouchEnd = () => {
|
|
1479
|
-
if (!closeOnSwipeDown ||
|
|
1693
|
+
if (!closeOnSwipeDown || touchStart === null || touchEnd === null) {
|
|
1694
|
+
resetTouchState();
|
|
1480
1695
|
return;
|
|
1696
|
+
}
|
|
1481
1697
|
const distance = touchEnd - touchStart;
|
|
1482
|
-
const isSwipeDown = distance >
|
|
1483
|
-
|
|
1698
|
+
const isSwipeDown = distance > 80; // Minimum swipe distance
|
|
1699
|
+
// Only close if:
|
|
1700
|
+
// 1. Swiped from drag handle, OR
|
|
1701
|
+
// 2. Body is scrolled to top and user swiped down
|
|
1702
|
+
const bodyScrolledToTop = (bodyRef.current?.scrollTop || 0) <= 0;
|
|
1703
|
+
const shouldClose = isSwipeDown && isSwipeGesture && (isDraggingFromHandle.current || bodyScrolledToTop);
|
|
1704
|
+
if (shouldClose) {
|
|
1484
1705
|
handleClose();
|
|
1485
1706
|
}
|
|
1707
|
+
resetTouchState();
|
|
1708
|
+
};
|
|
1709
|
+
const resetTouchState = () => {
|
|
1486
1710
|
setTouchStart(null);
|
|
1487
1711
|
setTouchEnd(null);
|
|
1712
|
+
setTouchStartX(null);
|
|
1713
|
+
setIsSwipeGesture(false);
|
|
1714
|
+
isDraggingFromHandle.current = false;
|
|
1488
1715
|
};
|
|
1716
|
+
// Handle Android back button
|
|
1717
|
+
React.useEffect(() => {
|
|
1718
|
+
if (!isOpen || !isAndroid())
|
|
1719
|
+
return;
|
|
1720
|
+
const handlePopState = (e) => {
|
|
1721
|
+
e.preventDefault();
|
|
1722
|
+
handleClose();
|
|
1723
|
+
};
|
|
1724
|
+
// Push a dummy state so back button can be caught
|
|
1725
|
+
window.history.pushState({ bottomSheet: true }, "");
|
|
1726
|
+
window.addEventListener("popstate", handlePopState);
|
|
1727
|
+
return () => {
|
|
1728
|
+
window.removeEventListener("popstate", handlePopState);
|
|
1729
|
+
// Clean up the dummy state if still present
|
|
1730
|
+
if (window.history.state?.bottomSheet) {
|
|
1731
|
+
window.history.back();
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
}, [isOpen, handleClose]);
|
|
1489
1735
|
// Don't render if not open and not closing
|
|
1490
1736
|
if (!isOpen && !isClosing)
|
|
1491
1737
|
return null;
|
|
1492
1738
|
const hasHeader = title || description;
|
|
1493
|
-
|
|
1739
|
+
// Calculate dynamic max height based on keyboard
|
|
1740
|
+
const dynamicMaxHeight = keyboardHeight > 0
|
|
1741
|
+
? `calc(${viewportHeight}px - ${safeAreaBottom}px - 20px)`
|
|
1742
|
+
: maxHeight;
|
|
1743
|
+
return (jsxs("div", { className: cn("fixed inset-0 z-9999 flex items-end justify-center",
|
|
1744
|
+
// Use visual viewport height on mobile when keyboard is open
|
|
1745
|
+
keyboardHeight > 0 && "items-start pt-[env(safe-area-inset-top)]", className), style: {
|
|
1746
|
+
// Use visual viewport height when keyboard is open
|
|
1747
|
+
height: keyboardHeight > 0 ? `${viewportHeight}px` : "100%",
|
|
1748
|
+
}, 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
|
+
// Ensure bottom sheet is at the bottom when keyboard is open
|
|
1750
|
+
keyboardHeight > 0 && "mt-auto", contentClassName), style: {
|
|
1751
|
+
maxHeight: dynamicMaxHeight,
|
|
1752
|
+
// Add safe area padding at the bottom for devices with home indicator
|
|
1753
|
+
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",
|
|
1755
|
+
// 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: {
|
|
1757
|
+
// Add safe area padding to footer
|
|
1758
|
+
paddingBottom: `max(env(safe-area-inset-bottom, 0px), ${safeAreaBottom}px)`,
|
|
1759
|
+
}, 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 })] }))] })] }));
|
|
1494
1760
|
});
|
|
1495
1761
|
BottomSheet.displayName = "BottomSheet";
|
|
1496
1762
|
|
|
@@ -4602,7 +4868,7 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
4602
4868
|
? true
|
|
4603
4869
|
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"), maxHeight: `${position.maxHeight}px`, unstyled: isMobile }));
|
|
4604
4870
|
// Mobile: BottomSheet, Desktop: Regular Dropdown
|
|
4605
|
-
const dropdownMenu = showDropdown && (isMobile ? (jsxs(BottomSheet, { isOpen: isOpen, onClose: () => setIsOpen(false), title: sectionHeading, variant: "default", showDragHandle: true, closeOnOverlayClick: true, closeOnEscape: true, closeOnSwipeDown: true, children: [jsx("div", { className: "mb-4", children: jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", autoFocus: true, ...textFieldProps }) }), renderDropdownContent()] })) : (jsx("div", { ref: menuRef, style: {
|
|
4871
|
+
const dropdownMenu = showDropdown && (isMobile ? (jsxs(BottomSheet, { isOpen: isOpen, onClose: () => setIsOpen(false), title: sectionHeading, variant: "default", showDragHandle: true, closeOnOverlayClick: true, closeOnEscape: true, closeOnSwipeDown: true, adjustForKeyboard: true, scrollToFocusedInput: true, maxHeight: "85vh", children: [jsx("div", { className: "mb-4 sticky top-0 bg-white z-10 -mx-6 px-6 pt-2 pb-4 -mt-4", children: jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", autoFocus: true, ...textFieldProps }) }), renderDropdownContent()] })) : (jsx("div", { ref: menuRef, style: {
|
|
4606
4872
|
position: "fixed",
|
|
4607
4873
|
...(position.top !== undefined && { top: `${position.top}px` }),
|
|
4608
4874
|
...(position.bottom !== undefined && {
|