Skip to content

Commit e75bba8

Browse files
committed
Close app menu when when outside click happens
1 parent a2064ab commit e75bba8

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

src/components/BorderedApp/BorderedAppMenu/BorderedAppMenu.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useRef, useState } from "react";
22
import "./BorderedAppMenu.scss";
33
import useSystemSettings from "../../../stores/systemSettingsStore";
4+
import useDetectClickOutside from "../../../hooks/useDetectClickOutside";
45

56
export interface BorderedAppMenuItemProps {
67
title: string;
@@ -155,6 +156,9 @@ function BorderedAppMenu({ title, items }: BorderedAppMenuProps) {
155156
position.current = { x: (rect?.left ?? 0) - 12, y: rect?.height ?? 0 };
156157
}, [elementRef]);
157158

159+
// Close the menu if an outside click occurs
160+
useDetectClickOutside({ elementRef, onClick: () => setOpen(false) });
161+
158162
return (
159163
<div
160164
ref={elementRef}

src/hooks/useDetectClickOutside.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { useEffect } from "react";
2+
3+
interface UseDetectClickOutsideProps<Element extends HTMLElement> {
4+
/**
5+
* A reference to the top-most element./
6+
* Clicks on any parents of this element will trigger the callback.
7+
* Clicking this element or any of it's children will not trigger the callback.
8+
*/
9+
elementRef: React.RefObject<Element>;
10+
11+
/**
12+
* The callback to be invoked when a click
13+
* has been detected outside
14+
*/
15+
onClick: () => void;
16+
}
17+
18+
/**
19+
* Detects a click that has occurred outside of the specified element.
20+
*/
21+
function useDetectClickOutside<Element extends HTMLElement>({
22+
elementRef,
23+
onClick,
24+
}: UseDetectClickOutsideProps<Element>) {
25+
useEffect(() => {
26+
function clickHandler(e: MouseEvent) {
27+
// If the element has been clicked on, we dont want to invoke the callback
28+
if (e.target === elementRef.current) return;
29+
30+
for (const child of elementRef.current?.childNodes ?? []) {
31+
// If any of the elements children have been clicked,
32+
// we dont want to invoke the callback
33+
if (e.target === child) return;
34+
}
35+
36+
// Click must be outside, so invoke callback
37+
onClick();
38+
}
39+
40+
window.addEventListener("click", clickHandler);
41+
42+
return () => window.removeEventListener("click", clickHandler);
43+
});
44+
}
45+
46+
export default useDetectClickOutside;

0 commit comments

Comments
 (0)