Better Dropdown size calculation for scrolling when not enough room

This commit is contained in:
Gregory Schier
2024-10-02 16:17:28 -07:00
parent e5c6c31e02
commit 4b7712df80
+17 -24
View File
@@ -229,17 +229,8 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
defaultSelectedIndex ?? null, defaultSelectedIndex ?? null,
[defaultSelectedIndex], [defaultSelectedIndex],
); );
const [menuStyles, setMenuStyles] = useState<CSSProperties>({});
const [filter, setFilter] = useState<string>(''); const [filter, setFilter] = useState<string>('');
// Calculate the max height so we can scroll
const initMenu = useCallback((el: HTMLDivElement | null) => {
if (el === null) return {};
const windowBox = document.documentElement.getBoundingClientRect();
const menuBox = el.getBoundingClientRect();
setMenuStyles({ maxHeight: windowBox.height - menuBox.top - 5 });
}, []);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
onClose(); onClose();
setSelectedIndex(null); setSelectedIndex(null);
@@ -355,11 +346,12 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
[handleClose, handleNext, handlePrev, handleSelect, items, selectedIndex], [handleClose, handleNext, handlePrev, handleSelect, items, selectedIndex],
); );
const { containerStyles, triangleStyles } = useMemo<{ const styles = useMemo<{
containerStyles: CSSProperties; container: CSSProperties;
triangleStyles: CSSProperties | null; menu: CSSProperties;
triangle: CSSProperties;
}>(() => { }>(() => {
if (triggerShape == null) return { containerStyles: {}, triangleStyles: null }; if (triggerShape == null) return { container: {}, triangle: {}, menu: {} };
const menuMarginY = 5; const menuMarginY = 5;
const docRect = document.documentElement.getBoundingClientRect(); const docRect = document.documentElement.getBoundingClientRect();
@@ -371,7 +363,8 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
const onRight = horizontalSpaceRemaining < 200; const onRight = horizontalSpaceRemaining < 200;
const upsideDown = heightBelow < heightAbove && heightBelow < items.length * 25 + 20 + 200; const upsideDown = heightBelow < heightAbove && heightBelow < items.length * 25 + 20 + 200;
const triggerWidth = triggerShape.right - triggerShape.left; const triggerWidth = triggerShape.right - triggerShape.left;
const containerStyles = { return {
container: {
top: !upsideDown ? top + menuMarginY : undefined, top: !upsideDown ? top + menuMarginY : undefined,
bottom: upsideDown bottom: upsideDown
? docRect.height - top - (triggerShape.top - triggerShape.bottom) + menuMarginY ? docRect.height - top - (triggerShape.top - triggerShape.bottom) + menuMarginY
@@ -380,8 +373,8 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
left: !onRight ? triggerShape.left : undefined, left: !onRight ? triggerShape.left : undefined,
minWidth: fullWidth ? triggerWidth : undefined, minWidth: fullWidth ? triggerWidth : undefined,
maxWidth: '40rem', maxWidth: '40rem',
}; },
const triangleStyles: CSSProperties = { triangle: {
width: '0.4rem', width: '0.4rem',
height: '0.4rem', height: '0.4rem',
...(onRight ...(onRight
@@ -390,8 +383,11 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
...(upsideDown ...(upsideDown
? { bottom: '-0.2rem', rotate: '225deg' } ? { bottom: '-0.2rem', rotate: '225deg' }
: { top: '-0.2rem', rotate: '45deg' }), : { top: '-0.2rem', rotate: '45deg' }),
},
menu: {
maxHeight: `${(upsideDown ? heightAbove : heightBelow) - 15}px`,
},
}; };
return { containerStyles, triangleStyles };
}, [fullWidth, items.length, triggerShape]); }, [fullWidth, items.length, triggerShape]);
const filteredItems = useMemo( const filteredItems = useMemo(
@@ -435,20 +431,18 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
role="menu" role="menu"
aria-orientation="vertical" aria-orientation="vertical"
dir="ltr" dir="ltr"
style={containerStyles} style={styles.container}
className={classNames(className, 'outline-none my-1 pointer-events-auto fixed z-50')} className={classNames(className, 'outline-none my-1 pointer-events-auto fixed z-50')}
> >
{triangleStyles && showTriangle && ( {showTriangle && (
<span <span
aria-hidden aria-hidden
style={triangleStyles} style={styles.triangle}
className="bg-surface absolute border-border-subtle border-t border-l" className="bg-surface absolute border-border-subtle border-t border-l"
/> />
)} )}
{containerStyles && (
<VStack <VStack
ref={initMenu} style={styles.menu}
style={menuStyles}
className={classNames( className={classNames(
className, className,
'h-auto bg-surface rounded-md shadow-lg py-1.5 border', 'h-auto bg-surface rounded-md shadow-lg py-1.5 border',
@@ -489,7 +483,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
); );
})} })}
</VStack> </VStack>
)}
</motion.div> </motion.div>
</div> </div>
</Overlay> </Overlay>