Skip to content

Commit

Permalink
feat(Overlay): add ref support (#5710)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyletsang committed May 18, 2021
1 parent 743a6e7 commit d08e434
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 68 deletions.
141 changes: 73 additions & 68 deletions src/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import BaseOverlay, {
} from 'react-overlays/Overlay';
import safeFindDOMNode from 'react-overlays/safeFindDOMNode';
import { componentOrElement, elementType } from 'prop-types-extra';
import useMergedRefs from '@restart/hooks/useMergedRefs';
import useOverlayOffset from './useOverlayOffset';
import Fade from './Fade';
import { TransitionType } from './helpers';
import { ArrowProps, Placement } from './types';
import { ArrowProps, Placement, RootCloseEvent } from './types';

export interface OverlayInjectedProps {
ref: React.RefCallback<HTMLElement>;
Expand All @@ -35,10 +36,11 @@ export type OverlayChildren =
| ((injected: OverlayInjectedProps) => React.ReactNode);

export interface OverlayProps
extends Omit<BaseOverlayProps, 'children' | 'transition'> {
extends Omit<BaseOverlayProps, 'children' | 'transition' | 'rootCloseEvent'> {
children: OverlayChildren;
transition?: TransitionType;
placement?: Placement;
rootCloseEvent?: RootCloseEvent;
}

const propTypes = {
Expand Down Expand Up @@ -72,7 +74,7 @@ const propTypes = {
/**
* Specify event for triggering a "root close" toggle.
*/
rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']),
rootCloseEvent: PropTypes.oneOf<RootCloseEvent>(['click', 'mousedown']),

/**
* A callback invoked by the overlay when it wishes to be hidden. Required if
Expand Down Expand Up @@ -119,7 +121,7 @@ const propTypes = {
/**
* The placement of the Overlay in relation to it's `target`.
*/
placement: PropTypes.oneOf([
placement: PropTypes.oneOf<Placement>([
'auto-start',
'auto',
'auto-end',
Expand All @@ -138,7 +140,7 @@ const propTypes = {
]),
};

const defaultProps = {
const defaultProps: Partial<OverlayProps> = {
transition: Fade,
rootClose: false,
show: false,
Expand All @@ -154,77 +156,80 @@ function wrapRefs(props, arrowProps) {
aRef.__wrapped || (aRef.__wrapped = (r) => aRef(safeFindDOMNode(r)));
}

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

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

return (
<BaseOverlay
{...outerProps}
ref={ref}
popperConfig={{
...popperConfig,
modifiers: modifiers.concat(popperConfig.modifiers || []),
}}
transition={actualTransition as any}
>
{({
props: overlayProps,
arrowProps,
show,
update,
forceUpdate: _,
placement,
state,
...props
}) => {
wrapRefs(overlayProps, arrowProps);
const popper = Object.assign(popperRef.current, {
state,
scheduleUpdate: update,
const Overlay = React.forwardRef<HTMLElement, OverlayProps>(
(
{ children: overlay, transition, popperConfig = {}, ...outerProps },
outerRef,
) => {
const popperRef = useRef({});
const [ref, modifiers] = useOverlayOffset();
const mergedRef = useMergedRefs(outerRef, ref);

const actualTransition =
transition === true ? Fade : transition || undefined;

return (
<BaseOverlay
{...outerProps}
ref={mergedRef}
popperConfig={{
...popperConfig,
modifiers: modifiers.concat(popperConfig.modifiers || []),
}}
transition={actualTransition}
>
{({
props: overlayProps,
arrowProps,
show,
update,
forceUpdate: _,
placement,
outOfBoundaries:
state?.modifiersData.hide?.isReferenceHidden || false,
});
state,
...props
}) => {
wrapRefs(overlayProps, arrowProps);
const popper = Object.assign(popperRef.current, {
state,
scheduleUpdate: update,
placement,
outOfBoundaries:
state?.modifiersData.hide?.isReferenceHidden || false,
});

if (typeof overlay === 'function')
return overlay({
if (typeof overlay === 'function')
return overlay({
...props,
...overlayProps,
placement,
show,
...(!transition && show && { className: 'show' }),
popper,
arrowProps,
});

return React.cloneElement(overlay as React.ReactElement, {
...props,
...overlayProps,
placement,
show,
...(!transition && show && { className: 'show' }),
popper,
arrowProps,
popper,
className: classNames(
(overlay as React.ReactElement).props.className,
!transition && show && 'show',
),
style: {
...(overlay as React.ReactElement).props.style,
...overlayProps.style,
},
});
}}
</BaseOverlay>
);
},
);

return React.cloneElement(overlay, {
...props,
...overlayProps,
placement,
arrowProps,
popper,
className: classNames(
overlay.props.className,
!transition && show && 'show',
),
style: {
...overlay.props.style,
...overlayProps.style,
},
});
}}
</BaseOverlay>
);
}

Overlay.displayName = 'Overlay';
Overlay.propTypes = propTypes;
Overlay.defaultProps = defaultProps;

Expand Down
2 changes: 2 additions & 0 deletions src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@ export const alignPropType = PropTypes.oneOfType([
PropTypes.shape({ xl: alignDirection }),
PropTypes.shape({ xxl: alignDirection }),
]);

export type RootCloseEvent = 'click' | 'mousedown';
38 changes: 38 additions & 0 deletions test/OverlaySpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as React from 'react';
import { mount } from 'enzyme';

import Overlay from '../src/Overlay';
import Popover from '../src/Popover';

describe('<Overlay>', () => {
it('should forward ref to the overlay', () => {
const ref = React.createRef();
mount(
<Overlay ref={ref} show>
<Popover id="my-overlay">test</Popover>
</Overlay>,
);

ref.current.id.should.equal('my-overlay');
});

it('should use Fade internally if transition=true', () => {
const wrapper = mount(
<Overlay show transition>
<Popover id="my-overlay">test</Popover>
</Overlay>,
);

expect(wrapper.find('Fade').exists()).to.be.true;
});

it('should not use Fade if transition=false', () => {
const wrapper = mount(
<Overlay show transition={false}>
<Popover id="my-overlay">test</Popover>
</Overlay>,
);

expect(wrapper.find('Fade').exists()).to.be.false;
});
});

0 comments on commit d08e434

Please sign in to comment.