Skip to content

Commit b2bf177

Browse files
authored
feat: allow renderProp pattern in OverlayTrigger (react-bootstrap#5316)
1 parent 26ee2b9 commit b2bf177

File tree

11 files changed

+107
-100
lines changed

11 files changed

+107
-100
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@
7676
"invariant": "^2.2.4",
7777
"prop-types": "^15.7.2",
7878
"prop-types-extra": "^1.1.0",
79-
"react-overlays": "^4.0.0",
80-
"react-transition-group": "^4.0.0",
79+
"react-overlays": "^4.1.0",
80+
"react-transition-group": "^4.4.1",
8181
"uncontrollable": "^7.0.0",
8282
"warning": "^4.0.3"
8383
},

src/OverlayTrigger.tsx

+25-49
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import useTimeout from '@restart/hooks/useTimeout';
55
import safeFindDOMNode from 'react-overlays/safeFindDOMNode';
66
import warning from 'warning';
77
import { useUncontrolledProp } from 'uncontrollable';
8-
import { Modifier } from 'react-overlays/esm/usePopper';
98
import Overlay, { OverlayChildren, OverlayProps } from './Overlay';
109

1110
export type OverlayTriggerType = 'hover' | 'click' | 'focus';
@@ -16,9 +15,15 @@ export type OverlayInjectedProps = {
1615
onFocus?: (...args: any[]) => any;
1716
};
1817

18+
export type OverlayTriggerRenderProps = OverlayInjectedProps & {
19+
ref: React.Ref<any>;
20+
};
21+
1922
export interface OverlayTriggerProps
2023
extends Omit<OverlayProps, 'children' | 'target'> {
21-
children: React.ReactElement;
24+
children:
25+
| React.ReactElement
26+
| ((props: OverlayTriggerRenderProps) => React.ReactNode);
2227
trigger?: OverlayTriggerType | OverlayTriggerType[];
2328
delay?: OverlayDelay;
2429
show?: boolean;
@@ -51,9 +56,9 @@ function normalizeDelay(delay?: OverlayDelay) {
5156
// for cases when the trigger is disabled and mouseOut/Over can cause flicker
5257
// moving from one child element to another.
5358
function handleMouseOverOut(
54-
handler: (...args: any[]) => any,
59+
handler: (...args: [React.MouseEvent, ...any[]]) => any,
5560
args: [React.MouseEvent, ...any[]],
56-
relatedNative,
61+
relatedNative: 'fromElement' | 'toElement',
5762
) {
5863
const [e] = args;
5964
const target = e.currentTarget;
@@ -188,9 +193,10 @@ function OverlayTrigger({
188193

189194
const delay = normalizeDelay(propsDelay);
190195

191-
const child = React.Children.only(children);
192-
193-
const { onFocus, onBlur, onClick } = child.props;
196+
const { onFocus, onBlur, onClick } =
197+
typeof children !== 'function'
198+
? React.Children.only(children).props
199+
: ({} as any);
194200

195201
const getTarget = useCallback(
196202
() => safeFindDOMNode(triggerNodeRef.current),
@@ -228,15 +234,15 @@ function OverlayTrigger({
228234
const handleFocus = useCallback(
229235
(...args: any[]) => {
230236
handleShow();
231-
if (onFocus) onFocus(...args);
237+
onFocus?.(...args);
232238
},
233239
[handleShow, onFocus],
234240
);
235241

236242
const handleBlur = useCallback(
237243
(...args: any[]) => {
238244
handleHide();
239-
if (onBlur) onBlur(...args);
245+
onBlur?.(...args);
240246
},
241247
[handleHide, onBlur],
242248
);
@@ -263,34 +269,6 @@ function OverlayTrigger({
263269
[handleHide],
264270
);
265271

266-
// We add aria-describedby in the case where the overlay is a role="tooltip"
267-
// for other cases describedby isn't appropriate (e.g. a popover with inputs) so we don't add it.
268-
const ariaModifier: Modifier<'ariaDescribedBy', Record<string, unknown>> = {
269-
name: 'ariaDescribedBy',
270-
enabled: true,
271-
phase: 'afterWrite',
272-
effect: ({ state }) => {
273-
return () => {
274-
if ('removeAttribute' in state.elements.reference)
275-
state.elements.reference.removeAttribute('aria-describedby');
276-
};
277-
},
278-
fn: ({ state }) => {
279-
const { popper, reference } = state.elements;
280-
281-
if (!show || !reference) return;
282-
283-
const role = popper.getAttribute('role') || '';
284-
if (
285-
popper.id &&
286-
role.toLowerCase() === 'tooltip' &&
287-
'setAttribute' in reference
288-
) {
289-
reference.setAttribute('aria-describedby', popper.id);
290-
}
291-
},
292-
};
293-
294272
const triggers: string[] = trigger == null ? [] : [].concat(trigger as any);
295273
const triggerProps: any = {};
296274

@@ -312,25 +290,23 @@ function OverlayTrigger({
312290
triggerProps.onMouseOut = handleMouseOut;
313291
}
314292

315-
// TODO: fix typing
316-
// @ts-ignore
317-
const modifiers = [ariaModifier].concat(popperConfig.modifiers || []);
318293
return (
319294
<>
320-
<RefHolder ref={triggerNodeRef}>
321-
{cloneElement(child as any, triggerProps)}
322-
</RefHolder>
295+
{typeof children === 'function' ? (
296+
children({ ...triggerProps, ref: triggerNodeRef })
297+
) : (
298+
<RefHolder ref={triggerNodeRef}>
299+
{cloneElement(children as any, triggerProps)}
300+
</RefHolder>
301+
)}
323302
<Overlay
324303
{...props}
325-
popperConfig={{
326-
...popperConfig,
327-
modifiers,
328-
}}
329304
show={show}
330305
onHide={handleHide}
331-
target={getTarget as any}
332-
placement={placement}
333306
flip={flip}
307+
placement={placement}
308+
popperConfig={popperConfig}
309+
target={getTarget as any}
334310
>
335311
{overlay}
336312
</Overlay>

www/src/components/Heading.js

+2-13
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,7 @@ const styles = css`
1010
composes: __heading from global;
1111
1212
position: relative;
13-
pointer-events: none;
14-
15-
&:before {
16-
display: block;
17-
height: 6rem;
18-
margin-top: -6rem;
19-
visibility: hidden;
20-
content: '';
21-
}
22-
}
23-
.inner {
24-
pointer-events: auto;
13+
scroll-margin-top: 5rem;
2514
}
2615
`;
2716

@@ -35,7 +24,7 @@ const Heading = ({ h, id, title, className, children, registerNode }) => {
3524
const H = `h${h}`;
3625
return (
3726
<H id={id} className={classNames(className, styles.heading)}>
38-
<div className={styles.inner}>{children}</div>
27+
{children}
3928
</H>
4029
);
4130
};

www/src/components/NavMain.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ const Banner = styled(Navbar).attrs({
3232
}
3333
3434
@include media-breakpoint-up(md) {
35-
position: sticky;
36-
top: 0rem;
3735
z-index: 1040;
3836
}
3937
`;
@@ -51,7 +49,7 @@ const StyledNavbar = styled(Navbar).attrs({
5149
5250
@include media-breakpoint-up(md) {
5351
position: sticky;
54-
top: 4rem;
52+
top: 0rem;
5553
z-index: 1040;
5654
}
5755
`;

www/src/components/SideNav.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ const MenuButton = styled(Button).attrs({ variant: 'link' })`
1818
const SidePanel = styled('div')`
1919
@import '../css/theme';
2020
21+
$top: 4rem;
22+
2123
composes: d-flex flex-column from global;
2224
2325
background-color: #f7f7f7;
2426
2527
@include media-breakpoint-up(md) {
2628
position: sticky;
27-
top: 4rem;
29+
top: $top;
2830
z-index: 1000;
29-
height: calc(100vh - 4rem);
31+
height: calc(100vh - #{$top});
3032
background-color: #f7f7f7;
3133
border-right: 1px solid $divider;
3234
}

www/src/components/Toc.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ export const TocContext = React.createContext();
66
const SidePanel = styled('div')`
77
@import '../css/theme';
88
9+
$top: 4rem;
10+
911
order: 2;
1012
position: sticky;
11-
top: 4rem;
12-
height: calc(100vh - 4rem);
13+
top: $top;
14+
height: calc(100vh - #{$top});
1315
padding-top: 1.5rem;
1416
padding-bottom: 1.5rem;
1517
font-size: 0.875rem;

www/src/examples/Overlays/OverlayTrigger.js

-19
This file was deleted.

www/src/examples/Overlays/Trigger.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const renderTooltip = (props) => (
2+
<Tooltip id="button-tooltip" {...props}>
3+
Simple tooltip
4+
</Tooltip>
5+
);
6+
7+
render(
8+
<OverlayTrigger
9+
placement="right"
10+
delay={{ show: 250, hide: 400 }}
11+
overlay={renderTooltip}
12+
>
13+
<Button variant="success">Hover me to see</Button>
14+
</OverlayTrigger>,
15+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
render(
2+
<OverlayTrigger
3+
placement="bottom"
4+
overlay={<Tooltip id="button-tooltip-2">Check out this avatar</Tooltip>}
5+
>
6+
{({ ref, ...triggerHandler }) => (
7+
<Button
8+
variant="light"
9+
{...triggerHandler}
10+
className="d-inline-flex align-items-center"
11+
>
12+
<Image
13+
ref={ref}
14+
roundedCircle
15+
src="holder.js/20x20?text=J&bg=28a745&fg=FFF"
16+
/>
17+
<span className="ml-1">Hover to see</span>
18+
</Button>
19+
)}
20+
</OverlayTrigger>,
21+
);

www/src/pages/components/overlays.js

+25-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { css } from 'astroturf';
55
import LinkedHeading from '../../components/LinkedHeading';
66
import ComponentApi from '../../components/ComponentApi';
77
import ReactPlayground from '../../components/ReactPlayground';
8+
import Callout from '../../components/Callout';
89

910
import Disabled from '../../examples/Overlays/Disabled';
1011
import Overlay from '../../examples/Overlays/Overlay';
11-
import OverlayTrigger from '../../examples/Overlays/OverlayTrigger';
12+
import OverlayTrigger from '../../examples/Overlays/Trigger';
13+
import TriggerRenderProp from '../../examples/Overlays/TriggerRenderProp';
1214
import PopoverBasic from '../../examples/Overlays/PopoverBasic';
1315
import PopoverContained from '../../examples/Overlays/PopoverContained';
1416
import PopoverPositioned from '../../examples/Overlays/PopoverPositioned';
@@ -91,7 +93,7 @@ export default withLayout(function TooltipSection({ data }) {
9193
</p>
9294
<ReactPlayground codeText={Overlay} />
9395

94-
<LinkedHeading h="3" id="overlay-trigger">
96+
<LinkedHeading h="2" id="overlay-trigger">
9597
OverlayTrigger
9698
</LinkedHeading>
9799
<p>
@@ -116,6 +118,27 @@ export default withLayout(function TooltipSection({ data }) {
116118

117119
<ReactPlayground codeText={OverlayTrigger} />
118120

121+
<LinkedHeading h="3" id="customizing-trigger-behavior">
122+
Customizing trigger behavior
123+
</LinkedHeading>
124+
125+
<p>
126+
For more advanced behaviors <code>{'<OverlayTrigger>'}</code> accepts a
127+
function child that passes in the injected <code>ref</code> and event
128+
handlers that coorespond to the configured <code>trigger</code> prop.
129+
</p>
130+
<p>
131+
You can manually apply the props to any element you want or split them
132+
up. The example below shows how to position the overlay to a different
133+
element than the one that triggers its visibility.
134+
</p>
135+
<Callout>
136+
<strong>Pro Tip:</strong> Using the function form of OverlayTrigger
137+
avoids a <code>React.findDOMNode</code> call, for those trying to be
138+
strict mode compliant.
139+
</Callout>
140+
<ReactPlayground codeText={TriggerRenderProp} />
141+
119142
<LinkedHeading h="2" id="tooltips">
120143
Tooltips
121144
</LinkedHeading>

yarn.lock

+8-8
Original file line numberDiff line numberDiff line change
@@ -7601,10 +7601,10 @@ react-lifecycles-compat@^3.0.4:
76017601
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
76027602
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
76037603

7604-
react-overlays@^4.0.0:
7605-
version "4.0.0"
7606-
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.0.0.tgz#7fbcb60d12fee3733e9e4dd216b225e94fb3befe"
7607-
integrity sha512-LpznWocwgeB5oWKg6cDdkqKP7MbX4ClKbJqgZGUMXPRBBYcqrgM6TjjZ/8DeurNU//GuqwQMjhmo/JVma4XEWw==
7604+
react-overlays@^4.1.0:
7605+
version "4.1.0"
7606+
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.1.0.tgz#755a890519b02e3904845172d5223ff2dfb1bb29"
7607+
integrity sha512-vdRpnKe0ckWOOD9uWdqykLUPHLPndIiUV7XfEKsi5008xiyHCfL8bxsx4LbMrfnxW1LzRthLyfy50XYRFNQqqw==
76087608
dependencies:
76097609
"@babel/runtime" "^7.4.5"
76107610
"@popperjs/core" "^2.0.0"
@@ -7625,10 +7625,10 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1:
76257625
react-is "^16.8.6"
76267626
scheduler "^0.19.1"
76277627

7628-
react-transition-group@^4.0.0:
7629-
version "4.3.0"
7630-
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"
7631-
integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==
7628+
react-transition-group@^4.4.1:
7629+
version "4.4.1"
7630+
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
7631+
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
76327632
dependencies:
76337633
"@babel/runtime" "^7.5.5"
76347634
dom-helpers "^5.0.1"

0 commit comments

Comments
 (0)