Skip to content

Commit c118c5d

Browse files
committed
Add useOutsideClick hook and documentation
1 parent 1dd0fd6 commit c118c5d

File tree

6 files changed

+144
-1
lines changed

6 files changed

+144
-1
lines changed

src/components/ListItem/DefaultListItem/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ const DefaultListItem = <T extends {}>(props: Props<T>) => {
4242
return (
4343
<li
4444
key={key}
45-
onClick={e => onClick(e)}
45+
onClick={e => {
46+
e.stopPropagation();
47+
onClick(e);
48+
}}
4649
onKeyDown={e => onKeyDown(e)}
4750
role='option'
4851
className={classNames(

src/hooks/useOutsideClick/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useEffect } from 'react';
2+
3+
export const useOutsideClick = (
4+
ref: React.RefObject<HTMLDivElement>,
5+
callback: (T?: any) => void
6+
) => {
7+
const handleClick = (e: Event) => {
8+
if (ref.current && !ref.current.contains(<HTMLElement>e.target)) {
9+
callback();
10+
}
11+
};
12+
13+
useEffect(() => {
14+
document.addEventListener('click', handleClick);
15+
16+
return () => {
17+
document.removeEventListener('click', handleClick);
18+
};
19+
}, [ref, callback]);
20+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.dropdown-wrapper {
2+
align-items: center;
3+
border: 1px solid var(--structure-color-600);
4+
border-radius: 4px;
5+
display: flex;
6+
flex-direction: column;
7+
height: 500px;
8+
justify-content: center;
9+
width: 500px;
10+
11+
--dropdown-width: 250px;
12+
}
13+
14+
.example-toast {
15+
--toast-top: 82px;
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# useOutsideClick
2+
3+
Accepts a `callback` as the first argument that is executed when the user clicks outside of an element that is passed as a `ref`
4+
as a second argument.
5+
6+
```javascript
7+
import { useRef } from 'react';
8+
import { useOutsideClick } from '/hooks/useOutsideClick';
9+
10+
const someComponent = () => {
11+
[...]
12+
const ref = useRef(null);
13+
14+
useOutsideClick(() => ref, alert('You clicked outside the dropdown'));
15+
16+
[...]
17+
}
18+
```
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import Dropdown from '@/components/Dropdown';
3+
import Toast from '@/components/Toast';
4+
import { useOutsideClick } from '@/hooks/useOutsideClick';
5+
import { Meta } from '@storybook/react';
6+
import styles from './useOutsideClick.stories.css';
7+
import mdx from './useOutsideClick.stories.mdx';
8+
9+
const Demo = () => {
10+
const [selectedItem, setSelectedItem] = useState({});
11+
const [wasClicked, setWasClicked] = useState(false);
12+
const ref = useRef(null);
13+
14+
useOutsideClick(ref, () => setWasClicked(true));
15+
16+
const options = [
17+
{
18+
label: 'Option 1',
19+
value: '1'
20+
},
21+
{
22+
label: 'Option 2',
23+
value: '2'
24+
}
25+
];
26+
27+
useEffect(() => {
28+
const timer = setTimeout(() => setWasClicked(false), 1000);
29+
30+
if (wasClicked) {
31+
/* eslint-disable no-unused-expressions */
32+
timer;
33+
}
34+
35+
return () => {
36+
clearTimeout(timer);
37+
};
38+
}, [wasClicked]);
39+
40+
const onChange = item => {
41+
setSelectedItem(item);
42+
};
43+
44+
return (
45+
<div className={styles['dropdown-wrapper']}>
46+
{wasClicked && (
47+
<Toast
48+
autoCloseInMilliseconds={1000}
49+
category='success'
50+
message='You clicked outside the Dropdown!'
51+
title='Success'
52+
variablesClassName={styles['example-toast']}
53+
/>
54+
)}
55+
56+
<h4>Click outside of the Dropdown</h4>
57+
<div ref={ref}>
58+
<Dropdown
59+
onChange={item => onChange(item)}
60+
options={options}
61+
getItemLabel={item => item.label}
62+
getItemKey={item => item.value}
63+
getItemValue={item => item.value}
64+
getListTitle={item => item.label}
65+
selectorText='Select'
66+
value={selectedItem}
67+
/>
68+
</div>
69+
</div>
70+
);
71+
};
72+
73+
export default {
74+
title: 'Hooks/useOutsideClick',
75+
component: Demo,
76+
parameters: {
77+
docs: {
78+
page: mdx
79+
}
80+
}
81+
} as Meta;
82+
83+
const Template = () => <Demo />;
84+
export const Default = Template.bind({});

src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ export { default as Toggle } from '@/components/Toggle';
2929
export { default as Tooltip } from '@/components/Tooltip';
3030
export { default as UploadAvatar } from '@/components/UploadAvatar';
3131
export * from '@/components/Grid';
32+
33+
export { useOutsideClick } from '@/hooks/useOutsideClick';

0 commit comments

Comments
 (0)