This repository has been archived by the owner on Jun 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: New DocumentClickListener component
- Loading branch information
1 parent
1890a9c
commit d3ee8dc
Showing
4 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React, {useState, useRef} from 'react'; | ||
import DocumentClickListener from './'; | ||
import Switch from '../Switch'; | ||
import Box from '../Box'; | ||
|
||
function DocumentClickExample() { | ||
const [count, setCount] = useState(0); | ||
const [isActive, setActive] = useState(true); | ||
const excludedElement = useRef(null); | ||
|
||
return ( | ||
<> | ||
<h1>Document clicks: {count}</h1> | ||
<p | ||
ref={excludedElement} | ||
style={{padding: '1em', border: '2px dashed grey'}} | ||
> | ||
Clicks inside this box will be ignored. | ||
</p> | ||
{isActive && ( | ||
<DocumentClickListener | ||
onClick={() => setCount(count + 1)} | ||
excludedElementRef={excludedElement} | ||
/> | ||
)} | ||
<Box mt="m"> | ||
<Switch | ||
checked={isActive} | ||
onChange={() => setActive(prevActive => !prevActive)} | ||
id="switch" | ||
/>{' '} | ||
<label htmlFor="switch">Count clicks</label> | ||
</Box> | ||
</> | ||
); | ||
} | ||
|
||
export default DocumentClickExample; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
name: DocumentClickListener | ||
menu: Components | ||
--- | ||
|
||
import {Playground, Props} from 'docz'; | ||
import DocumentClickListener from './'; | ||
import Example from './Example'; | ||
|
||
# DocumentClickListener | ||
|
||
A helper component that does not render any elements, but sets up a click listener on the body. | ||
If you want to ignore clicks on a certain element, you can pass in the ref of that element using the `excludedElementRef` prop. | ||
|
||
Useful for tooltips and dropdown menus. | ||
|
||
## Examples | ||
|
||
The below example shows how to use this component and also demonstrates why this is a component and not just a hook: Due to the "[rules of hooks](https://reactjs.org/docs/hooks-rules.html)", it's not possible to conditionally call a hook, while it's very easy to conditionally render a component. | ||
|
||
<Playground> | ||
<Example /> | ||
</Playground> | ||
|
||
```jsx | ||
function DocumentClickExample() { | ||
const [count, setCount] = useState(0); | ||
const [isActive, setActive] = useState(true); | ||
const excludedElement = useRef(null); | ||
return ( | ||
<> | ||
<h1>Document clicks: {count}</h1> | ||
<p | ||
ref={excludedElement} | ||
style={{padding: '1em', border: '2px dashed grey'}} | ||
> | ||
Clicks inside this box will be ignored. | ||
</p> | ||
{isActive && ( | ||
<DocumentClickListener | ||
onClick={() => setCount(count + 1)} | ||
excludedElementRef={excludedElement} | ||
/> | ||
)} | ||
<Box mt="m"> | ||
<Switch | ||
checked={isActive} | ||
onChange={() => setActive(prevActive => !prevActive)} | ||
id="switch" | ||
/>{' '} | ||
<label htmlFor="switch">Count clicks</label> | ||
</Box> | ||
</> | ||
); | ||
} | ||
``` | ||
|
||
## Props | ||
|
||
<Props of={DocumentClickListener} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import PropTypes from 'prop-types'; | ||
|
||
import useEventListener from '../useEventListener'; | ||
|
||
/** | ||
* Helper component that sets up a global click event listener. | ||
* Use the `excludedElement` prop to ignore clicks on that element. | ||
*/ | ||
|
||
function DocumentClickListener({onClick, excludedElementRef}) { | ||
useEventListener('click', event => { | ||
const excludedElement = | ||
excludedElementRef && excludedElementRef.current; | ||
// Bail out if the clicked element or the currently focused element | ||
// is inside of excludedElement. We need to check the focused element | ||
// to prevent an issue in Chrome where initiating a drag inside of an | ||
// input (to select the text inside of it) and ending that drag outside | ||
// of the input fires a click event, breaking our excludedElement rule. | ||
if ( | ||
excludedElement && | ||
(excludedElement === event.target || | ||
excludedElement.contains(event.target) || | ||
excludedElement === document.activeElement || | ||
excludedElement.contains(document.activeElement)) | ||
) { | ||
return null; | ||
} | ||
|
||
onClick(event); | ||
}); | ||
|
||
return null; | ||
} | ||
|
||
DocumentClickListener.propTypes = { | ||
onClick: PropTypes.func.isRequired, | ||
excludedElementRef: PropTypes.object, | ||
}; | ||
|
||
export default DocumentClickListener; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters