Skip to content

Commit

Permalink
fix: consider margins in Popper calculations (#5112)
Browse files Browse the repository at this point in the history
* fix: consider margins in Popper calculations

* WIP

* fix entire problem by removing margins

* remove extra popper stuff, fix for other elements

* fix test
  • Loading branch information
jquense committed Apr 20, 2020
1 parent 02484ef commit 52de702
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 200 deletions.
17 changes: 13 additions & 4 deletions src/DropdownMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useMergedRefs from '@restart/hooks/useMergedRefs';
import NavbarContext from './NavbarContext';
import { useBootstrapPrefix } from './ThemeProvider';
import useWrappedRefWithWarning from './useWrappedRefWithWarning';
import usePopperMarginModifiers from './usePopperMarginModifiers';

const propTypes = {
/**
Expand Down Expand Up @@ -64,17 +65,19 @@ const DropdownMenu = React.forwardRef(
alignRight,
rootCloseEvent,
flip,
popperConfig,
show: showProps,
renderOnMount,
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
as: Component = 'div',
popperConfig = {},
...props
},
ref,
) => {
const isNavbar = useContext(NavbarContext);
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown-menu');
const [popperRef, marginModifiers] = usePopperMarginModifiers();

const {
hasShown,
placement,
Expand All @@ -84,16 +87,22 @@ const DropdownMenu = React.forwardRef(
props: menuProps,
} = useDropdownMenu({
flip,
popperConfig,
rootCloseEvent,
show: showProps,
alignEnd: alignRight,
usePopper: !isNavbar,
popperConfig: {
...popperConfig,
modifiers: marginModifiers.concat(popperConfig.modifiers || []),
},
});

menuProps.ref = useMergedRefs(
menuProps.ref,
useWrappedRefWithWarning(ref, 'DropdownMenu'),
popperRef,
useMergedRefs(
useWrappedRefWithWarning(ref, 'DropdownMenu'),
menuProps.ref,
),
);

if (!hasShown && !renderOnMount) return null;
Expand Down
26 changes: 23 additions & 3 deletions src/Overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import BaseOverlay from 'react-overlays/Overlay';
import safeFindDOMNode from 'react-overlays/safeFindDOMNode';
import { componentOrElement, elementType } from 'prop-types-extra';

import usePopperMarginModifiers from './usePopperMarginModifiers';

import Fade from './Fade';

const propTypes = {
Expand Down Expand Up @@ -120,12 +122,27 @@ function wrapRefs(props, arrowProps) {
aRef.__wrapped || (aRef.__wrapped = (r) => aRef(safeFindDOMNode(r)));
}

function Overlay({ children: overlay, transition, ...outerProps }) {
function Overlay({
children: overlay,
transition,
popperConfig = {},
...outerProps
}) {
const popperRef = useRef({});
const [ref, marginModifiers] = usePopperMarginModifiers();

transition = transition === true ? Fade : transition || null;

return (
<BaseOverlay {...outerProps} transition={transition}>
<BaseOverlay
{...outerProps}
ref={ref}
popperConfig={{
...popperConfig,
modifiers: marginModifiers.concat(popperConfig.modifiers || []),
}}
transition={transition}
>
{({
props: overlayProps,
arrowProps,
Expand Down Expand Up @@ -164,7 +181,10 @@ function Overlay({ children: overlay, transition, ...outerProps }) {
overlay.props.className,
!transition && show && 'show',
),
style: { ...overlay.props.style, ...overlayProps.style },
style: {
...overlay.props.style,
...overlayProps.style,
},
});
}}
</BaseOverlay>
Expand Down
7 changes: 6 additions & 1 deletion src/Popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ const Popover = React.forwardRef(
)}
{...props}
>
<div className="arrow" {...arrowProps} />
<div
className="arrow"
{...arrowProps}
// this prevents an error if you render a Popover without arrow props, like in a test
style={arrowProps ? { ...arrowProps.style, margin: 0 } : undefined}
/>
{content ? <PopoverContent>{children}</PopoverContent> : children}
</div>
);
Expand Down
57 changes: 57 additions & 0 deletions src/usePopperMarginModifiers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useCallback, useRef, useMemo } from 'react';
import hasClass from 'dom-helpers/hasClass';

function getMargins(element) {
const styles = getComputedStyle(element);

const top = parseFloat(styles.marginTop) || 0;
const right = parseFloat(styles.marginRight) || 0;
const bottom = parseFloat(styles.marginBottom) || 0;
const left = parseFloat(styles.marginLeft) || 0;
return { top, right, bottom, left };
}

export default function usePopperMarginModifiers() {
const overlayRef = useRef(null);
const margins = useRef(null);

return [
useCallback((overlay) => {
if (
!overlay ||
!(hasClass(overlay, 'popover') || hasClass(overlay, 'dropdown-menu'))
)
return;

margins.current = getMargins(overlay);
overlay.style.margin = 0;
overlayRef.current = overlay;
}, []),
[
useMemo(() => {
return {
name: 'offset',
options: {
offset: ({ placement }) => {
if (!margins.current) return [0, 0];
const { top, left, bottom, right } = margins.current;

switch (placement.split('-')[0]) {
case 'top':
return [0, bottom];
case 'left':
return [0, right];
case 'bottom':
return [0, top];
case 'right':
return [0, left];
default:
return [0, 0];
}
},
},
};
}, [margins]),
],
];
}
Loading

0 comments on commit 52de702

Please sign in to comment.