Skip to content

Commit d08e434

Browse files
authored
feat(Overlay): add ref support (react-bootstrap#5710)
1 parent 743a6e7 commit d08e434

File tree

3 files changed

+113
-68
lines changed

3 files changed

+113
-68
lines changed

src/Overlay.tsx

Lines changed: 73 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import BaseOverlay, {
77
} from 'react-overlays/Overlay';
88
import safeFindDOMNode from 'react-overlays/safeFindDOMNode';
99
import { componentOrElement, elementType } from 'prop-types-extra';
10+
import useMergedRefs from '@restart/hooks/useMergedRefs';
1011
import useOverlayOffset from './useOverlayOffset';
1112
import Fade from './Fade';
1213
import { TransitionType } from './helpers';
13-
import { ArrowProps, Placement } from './types';
14+
import { ArrowProps, Placement, RootCloseEvent } from './types';
1415

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

3738
export interface OverlayProps
38-
extends Omit<BaseOverlayProps, 'children' | 'transition'> {
39+
extends Omit<BaseOverlayProps, 'children' | 'transition' | 'rootCloseEvent'> {
3940
children: OverlayChildren;
4041
transition?: TransitionType;
4142
placement?: Placement;
43+
rootCloseEvent?: RootCloseEvent;
4244
}
4345

4446
const propTypes = {
@@ -72,7 +74,7 @@ const propTypes = {
7274
/**
7375
* Specify event for triggering a "root close" toggle.
7476
*/
75-
rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']),
77+
rootCloseEvent: PropTypes.oneOf<RootCloseEvent>(['click', 'mousedown']),
7678

7779
/**
7880
* A callback invoked by the overlay when it wishes to be hidden. Required if
@@ -119,7 +121,7 @@ const propTypes = {
119121
/**
120122
* The placement of the Overlay in relation to it's `target`.
121123
*/
122-
placement: PropTypes.oneOf([
124+
placement: PropTypes.oneOf<Placement>([
123125
'auto-start',
124126
'auto',
125127
'auto-end',
@@ -138,7 +140,7 @@ const propTypes = {
138140
]),
139141
};
140142

141-
const defaultProps = {
143+
const defaultProps: Partial<OverlayProps> = {
142144
transition: Fade,
143145
rootClose: false,
144146
show: false,
@@ -154,77 +156,80 @@ function wrapRefs(props, arrowProps) {
154156
aRef.__wrapped || (aRef.__wrapped = (r) => aRef(safeFindDOMNode(r)));
155157
}
156158

157-
function Overlay({
158-
children: overlay,
159-
transition,
160-
popperConfig = {},
161-
...outerProps
162-
}: OverlayProps) {
163-
const popperRef = useRef({});
164-
const [ref, modifiers] = useOverlayOffset();
165-
166-
const actualTransition = transition === true ? Fade : transition || null;
167-
168-
return (
169-
<BaseOverlay
170-
{...outerProps}
171-
ref={ref}
172-
popperConfig={{
173-
...popperConfig,
174-
modifiers: modifiers.concat(popperConfig.modifiers || []),
175-
}}
176-
transition={actualTransition as any}
177-
>
178-
{({
179-
props: overlayProps,
180-
arrowProps,
181-
show,
182-
update,
183-
forceUpdate: _,
184-
placement,
185-
state,
186-
...props
187-
}) => {
188-
wrapRefs(overlayProps, arrowProps);
189-
const popper = Object.assign(popperRef.current, {
190-
state,
191-
scheduleUpdate: update,
159+
const Overlay = React.forwardRef<HTMLElement, OverlayProps>(
160+
(
161+
{ children: overlay, transition, popperConfig = {}, ...outerProps },
162+
outerRef,
163+
) => {
164+
const popperRef = useRef({});
165+
const [ref, modifiers] = useOverlayOffset();
166+
const mergedRef = useMergedRefs(outerRef, ref);
167+
168+
const actualTransition =
169+
transition === true ? Fade : transition || undefined;
170+
171+
return (
172+
<BaseOverlay
173+
{...outerProps}
174+
ref={mergedRef}
175+
popperConfig={{
176+
...popperConfig,
177+
modifiers: modifiers.concat(popperConfig.modifiers || []),
178+
}}
179+
transition={actualTransition}
180+
>
181+
{({
182+
props: overlayProps,
183+
arrowProps,
184+
show,
185+
update,
186+
forceUpdate: _,
192187
placement,
193-
outOfBoundaries:
194-
state?.modifiersData.hide?.isReferenceHidden || false,
195-
});
188+
state,
189+
...props
190+
}) => {
191+
wrapRefs(overlayProps, arrowProps);
192+
const popper = Object.assign(popperRef.current, {
193+
state,
194+
scheduleUpdate: update,
195+
placement,
196+
outOfBoundaries:
197+
state?.modifiersData.hide?.isReferenceHidden || false,
198+
});
196199

197-
if (typeof overlay === 'function')
198-
return overlay({
200+
if (typeof overlay === 'function')
201+
return overlay({
202+
...props,
203+
...overlayProps,
204+
placement,
205+
show,
206+
...(!transition && show && { className: 'show' }),
207+
popper,
208+
arrowProps,
209+
});
210+
211+
return React.cloneElement(overlay as React.ReactElement, {
199212
...props,
200213
...overlayProps,
201214
placement,
202-
show,
203-
...(!transition && show && { className: 'show' }),
204-
popper,
205215
arrowProps,
216+
popper,
217+
className: classNames(
218+
(overlay as React.ReactElement).props.className,
219+
!transition && show && 'show',
220+
),
221+
style: {
222+
...(overlay as React.ReactElement).props.style,
223+
...overlayProps.style,
224+
},
206225
});
226+
}}
227+
</BaseOverlay>
228+
);
229+
},
230+
);
207231

208-
return React.cloneElement(overlay, {
209-
...props,
210-
...overlayProps,
211-
placement,
212-
arrowProps,
213-
popper,
214-
className: classNames(
215-
overlay.props.className,
216-
!transition && show && 'show',
217-
),
218-
style: {
219-
...overlay.props.style,
220-
...overlayProps.style,
221-
},
222-
});
223-
}}
224-
</BaseOverlay>
225-
);
226-
}
227-
232+
Overlay.displayName = 'Overlay';
228233
Overlay.propTypes = propTypes;
229234
Overlay.defaultProps = defaultProps;
230235

src/types.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,5 @@ export const alignPropType = PropTypes.oneOfType([
6363
PropTypes.shape({ xl: alignDirection }),
6464
PropTypes.shape({ xxl: alignDirection }),
6565
]);
66+
67+
export type RootCloseEvent = 'click' | 'mousedown';

test/OverlaySpec.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as React from 'react';
2+
import { mount } from 'enzyme';
3+
4+
import Overlay from '../src/Overlay';
5+
import Popover from '../src/Popover';
6+
7+
describe('<Overlay>', () => {
8+
it('should forward ref to the overlay', () => {
9+
const ref = React.createRef();
10+
mount(
11+
<Overlay ref={ref} show>
12+
<Popover id="my-overlay">test</Popover>
13+
</Overlay>,
14+
);
15+
16+
ref.current.id.should.equal('my-overlay');
17+
});
18+
19+
it('should use Fade internally if transition=true', () => {
20+
const wrapper = mount(
21+
<Overlay show transition>
22+
<Popover id="my-overlay">test</Popover>
23+
</Overlay>,
24+
);
25+
26+
expect(wrapper.find('Fade').exists()).to.be.true;
27+
});
28+
29+
it('should not use Fade if transition=false', () => {
30+
const wrapper = mount(
31+
<Overlay show transition={false}>
32+
<Popover id="my-overlay">test</Popover>
33+
</Overlay>,
34+
);
35+
36+
expect(wrapper.find('Fade').exists()).to.be.false;
37+
});
38+
});

0 commit comments

Comments
 (0)