Skip to content

Commit

Permalink
Add support for React portal
Browse files Browse the repository at this point in the history
  • Loading branch information
ooade committed Dec 1, 2020
1 parent 3e18fcf commit cca228d
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ yarn add react-click-away-listener
- It's quite small in size.
- It's built with TypeScript.
- It supports both Mouse and Touch Events.
- It supports React Portal.

## Usage

Expand Down
43 changes: 42 additions & 1 deletion __tests__/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { render, fireEvent } from '@testing-library/react';
import ClickAwayListener from '../src';

Expand Down Expand Up @@ -138,4 +139,44 @@ describe('ClickAway Listener', () => {
fireEvent.click(getByTestId('some-other-button-two'));
expect(fakeHandleClick2).toBeCalledTimes(7);
});

it('should work with Portals', () => {
const fakeHandleClick = jest.fn();
let modalRoot = document.getElementById('modal-root');
if (!modalRoot) {
modalRoot = document.createElement('div');
modalRoot.setAttribute('id', 'modal-root');
document.body.appendChild(modalRoot);
}

const Modal = ({ children }) => {
const modalRoot = document.getElementById('modal-root');
const element = document.createElement('div');

useEffect(() => {
modalRoot.appendChild(element);

return () => {
modalRoot.removeChild(element);
};
});

return ReactDOM.createPortal(children, element);
};

const { getByText } = render(
<React.Fragment>
<ClickAwayListener isPortal={true} onClickAway={fakeHandleClick}>
<Modal>
<div>Hello World</div>
</Modal>
</ClickAwayListener>
<button>A button</button>
</React.Fragment>
);

fireEvent.click(getByText(/A button/i));
fireEvent.click(getByText(/Hello World/i));
expect(fakeHandleClick).toBeCalledTimes(1);
});
});
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-click-away-listener",
"version": "1.4.4",
"version": "1.5.0-beta.1",
"description": "A simple click away listener built with React Hooks",
"main": "dist/react-click-away-listener.js",
"module": "dist/react-click-away-listener.es.js",
Expand All @@ -25,6 +25,9 @@
],
"author": "Ademola Adegbuyi <ooade96@gmail.com>",
"license": "MIT",
"repository": {
"url": "https://github.com/ooade/react-click-away-listener"
},
"devDependencies": {
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
Expand All @@ -46,16 +49,16 @@
"jest": "^24.9.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rimraf": "^3.0.2",
"rollup": "^1.26.3",
"rollup-plugin-terser": "^7.0.0",
"typescript": "^3.6.4"
},
"peerDependencies": {
"react": "^16.11.0",
"react-dom": "^16.11.0"
"react": "^17.0.1",
"react-dom": "^17.0.1"
},
"jest": {
"collectCoverageFrom": [
Expand Down
35 changes: 29 additions & 6 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import React, { useEffect, useRef, FunctionComponent } from 'react';
import React, {
useRef,
useEffect,
MutableRefObject,
FunctionComponent
} from 'react';

type MouseEvents = 'click' | 'mousedown' | 'mouseup';
type TouchEvents = 'touchstart' | 'touchend';
type Events = MouseEvent | TouchEvent;

interface Props extends React.HTMLAttributes<HTMLElement> {
onClickAway: (event: MouseEvent | TouchEvent) => void;
onClickAway: (event: Events) => void;
isPortal?: boolean;
mouseEvent?: MouseEvents;
touchEvent?: TouchEvents;
as?: React.ElementType;
Expand All @@ -13,15 +20,24 @@ interface Props extends React.HTMLAttributes<HTMLElement> {
const ClickAwayListener: FunctionComponent<Props> = ({
as = 'div',
onClickAway,
isPortal = false,
mouseEvent = 'click',
touchEvent = 'touchend',
...props
}) => {
let node = useRef<HTMLElement>(null);
const node = useRef<HTMLElement>(null);
const portalEvent: MutableRefObject<Events | null> = useRef(null);

const handlePortalEvents = (event: Events | null) => {
portalEvent.current = event;
};

useEffect(() => {
const handleEvents = (event: MouseEvent | TouchEvent): void => {
if (node.current && node.current.contains(event.target as Node)) {
const handleEvents = (event: Events): void => {
if (
(node.current && node.current.contains(event.target as Node)) ||
(portalEvent.current && portalEvent.current.target === event.target)
) {
return;
}

Expand All @@ -37,7 +53,14 @@ const ClickAwayListener: FunctionComponent<Props> = ({
};
}, [mouseEvent, onClickAway, touchEvent]);

return React.createElement(as, { ref: node, ...props });
return React.createElement(as, {
ref: node,
...(isPortal && {
onClick: handlePortalEvents,
onTouchEnd: handlePortalEvents
}),
...props
});
};

export default ClickAwayListener;
30 changes: 14 additions & 16 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4420,7 +4420,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.4"

prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
Expand Down Expand Up @@ -4459,29 +4459,27 @@ randombytes@^2.1.0:
dependencies:
safe-buffer "^5.1.0"

react-dom@^16.11.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
react-dom@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.19.1"
scheduler "^0.20.1"

react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==

react@^16.11.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
react@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"

read-pkg-up@^2.0.0:
version "2.0.0"
Expand Down Expand Up @@ -4806,10 +4804,10 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==

scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
scheduler@^0.20.1:
version "0.20.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
Expand Down

0 comments on commit cca228d

Please sign in to comment.