From b214baab3e17d92f784b4f782863deafc5558ee4 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Wed, 18 May 2022 14:27:11 +0200
Subject: chore: close toolbar modals on click/focus outside

---
 src/utils/hooks/use-click-outside.tsx | 43 +++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 src/utils/hooks/use-click-outside.tsx

(limited to 'src/utils/hooks/use-click-outside.tsx')

diff --git a/src/utils/hooks/use-click-outside.tsx b/src/utils/hooks/use-click-outside.tsx
new file mode 100644
index 0000000..066c1c2
--- /dev/null
+++ b/src/utils/hooks/use-click-outside.tsx
@@ -0,0 +1,43 @@
+import { RefObject, useCallback, useEffect } from 'react';
+
+/**
+ * Listen for click/focus outside an element and execute the given callback.
+ *
+ * @param el - A React reference to an element.
+ * @param callback - A callback function to execute on click outside.
+ */
+const useClickOutside = (el: RefObject<HTMLElement>, callback: () => void) => {
+  /**
+   * Check if an event target is outside an element.
+   *
+   * @param {RefObject<HTMLElement>} ref - A React reference object.
+   * @param {EventTarget} target - An event target.
+   * @returns {boolean} True if the event target is outside the ref object.
+   */
+  const isTargetOutside = (
+    ref: RefObject<HTMLElement>,
+    target: EventTarget
+  ): boolean => {
+    if (!ref.current) return false;
+    return !ref.current.contains(target as Node);
+  };
+
+  const handleEvent = useCallback(
+    (e: MouseEvent | FocusEvent) => {
+      if (e.target && isTargetOutside(el, e.target)) callback();
+    },
+    [el, callback]
+  );
+
+  useEffect(() => {
+    document.addEventListener('mousedown', handleEvent);
+    document.addEventListener('focusin', handleEvent);
+
+    return () => {
+      document.removeEventListener('mousedown', handleEvent);
+      document.removeEventListener('focusin', handleEvent);
+    };
+  }, [handleEvent]);
+};
+
+export default useClickOutside;
-- 
cgit v1.2.3

