Skip to content

Commit 6e83459

Browse files
authored
feat: add hook useOnClickOutside (#1006)
1 parent 3fb7de9 commit 6e83459

File tree

5 files changed

+91
-0
lines changed

5 files changed

+91
-0
lines changed

src/components/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,4 @@ export * from './utils/useFileInput/useFileInput';
6969
export {useActionHandlers} from './utils/useActionHandlers';
7070
export {useUniqId} from './utils/useUniqId';
7171
export {getLayersCount} from './utils/LayerManager';
72+
export * from './utils/useOutsideClick';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* eslint-disable jsx-a11y/click-events-have-key-events */
2+
import React from 'react';
3+
4+
import {useOutsideClick} from '../useOutsideClick';
5+
6+
export const Demo = () => {
7+
const observerRef = React.useRef(null);
8+
const [status, setStatus] = React.useState<1 | 0>(0);
9+
10+
const handleOutsideClick = () => {
11+
setStatus(0);
12+
};
13+
14+
const handleClick = () => {
15+
setStatus(1);
16+
};
17+
18+
useOutsideClick({
19+
ref: observerRef,
20+
handler: handleOutsideClick,
21+
});
22+
23+
return (
24+
<div>
25+
<h1>{status}</h1>
26+
<div ref={observerRef} onClick={handleClick}>
27+
{'Target'}
28+
</div>
29+
<div>{'Outside'}</div>
30+
</div>
31+
);
32+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
3+
import {render, screen} from '@testing-library/react';
4+
import userEvent from '@testing-library/user-event';
5+
6+
import {Demo} from './Demo';
7+
8+
test('Check useOutsideClick correct work', async () => {
9+
render(<Demo />);
10+
11+
expect(screen.getByRole('heading')).toHaveTextContent('0');
12+
13+
await userEvent.click(screen.getByText('Target'));
14+
15+
expect(screen.getByRole('heading')).toHaveTextContent('1');
16+
17+
await userEvent.click(screen.getByText('Outside'));
18+
19+
expect(screen.getByRole('heading')).toHaveTextContent('0');
20+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export {useOutsideClick} from './useOutsideClick';
2+
export type {UseOutsideClickProps} from './useOutsideClick';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
3+
export interface UseOutsideClickProps<T> {
4+
ref: React.RefObject<T>;
5+
handler?: () => void;
6+
}
7+
8+
type UseOutsideClickType = <K extends HTMLElement>(props: UseOutsideClickProps<K>) => void;
9+
10+
/**
11+
* Hook for observing clicks outside a given target
12+
*
13+
* @param ref - purpose of observation
14+
* @param handler - callback when a click is triggered outside the observation target
15+
*
16+
* @return - nothing
17+
*/
18+
export const useOutsideClick: UseOutsideClickType = ({ref, handler}) => {
19+
React.useEffect(() => {
20+
const callback = (e: MouseEvent | TouchEvent) => {
21+
const elem = ref?.current;
22+
23+
if (elem && !elem.contains(e.target as Node) && handler) {
24+
handler();
25+
}
26+
};
27+
28+
window.addEventListener('mouseup', callback, {capture: true});
29+
window.addEventListener('touchend', callback, {capture: true});
30+
31+
return () => {
32+
window.removeEventListener('mouseup', callback, {capture: true});
33+
window.removeEventListener('touchend', callback, {capture: true});
34+
};
35+
}, [handler, ref]);
36+
};

0 commit comments

Comments
 (0)