summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-06-08 12:07:08 +0200
committerArmand Philippot <git@armandphilippot.com>2022-06-08 12:19:28 +0200
commit58cb40f031f395ca9efccff674ba0f2dae723f50 (patch)
treed69539a66a83da85689b8f7e835492eedc068e1f /src
parent43bddd9506d790ad6707fe71f28a4ecfa635c8f1 (diff)
fix(settings): close tooltip when modal is closing
The event was not captured so the tooltip remained open when the settings was closed. It prevented to click on the toolbar buttons.
Diffstat (limited to 'src')
-rw-r--r--src/components/molecules/forms/fieldset.tsx25
-rw-r--r--src/components/molecules/forms/radio-group.tsx10
-rw-r--r--src/components/organisms/modals/settings-modal.module.scss1
-rw-r--r--src/components/organisms/modals/settings-modal.tsx1
-rw-r--r--src/components/organisms/toolbar/toolbar.tsx21
-rw-r--r--src/utils/hooks/use-click-outside.tsx46
-rw-r--r--src/utils/hooks/use-on-click-outside.tsx52
7 files changed, 83 insertions, 73 deletions
diff --git a/src/components/molecules/forms/fieldset.tsx b/src/components/molecules/forms/fieldset.tsx
index 670cfa1..dae3ead 100644
--- a/src/components/molecules/forms/fieldset.tsx
+++ b/src/components/molecules/forms/fieldset.tsx
@@ -1,4 +1,4 @@
-import useClickOutside from '@utils/hooks/use-click-outside';
+import useOnClickOutside from '@utils/hooks/use-on-click-outside';
import {
cloneElement,
FC,
@@ -33,11 +33,15 @@ export type FieldsetProps = {
*/
legendClassName?: string;
/**
- * The legend position. Default: stacked.
+ * The legend position.
+ *
+ * @default 'stacked'
*/
legendPosition?: 'inline' | 'stacked';
/**
- * An accessible role. Default: group.
+ * An accessible role.
+ *
+ * @default 'group'
*/
role?: 'group' | 'radiogroup' | 'presentation' | 'none';
/**
@@ -63,7 +67,6 @@ const Fieldset: FC<FieldsetProps> = ({
}) => {
const [isTooltipOpened, setIsTooltipOpened] = useState<boolean>(false);
const buttonRef = useRef<HTMLButtonElement>(null);
- const tooltipRef = useRef<HTMLDivElement>(null);
const wrapperModifier = `wrapper--${legendPosition}`;
const buttonModifier = isTooltipOpened ? styles['btn--activated'] : '';
const legendModifier =
@@ -73,19 +76,17 @@ const Fieldset: FC<FieldsetProps> = ({
: 'tooltip--hidden';
/**
- * Close the tooltip if the event target is outside.
+ * Close the tooltip if the target is not the button.
*
- * @param {EventTarget} target - The event target.
+ * @param {Node} target - The event target.
*/
- const closeTooltip = (target: EventTarget) => {
- if (buttonRef.current && !buttonRef.current.contains(target as Node))
+ const closeTooltip = (target: Node) => {
+ if (buttonRef.current && !buttonRef.current.contains(target)) {
setIsTooltipOpened(false);
+ }
};
- useClickOutside(
- tooltipRef,
- (target) => isTooltipOpened && closeTooltip(target)
- );
+ const tooltipRef = useOnClickOutside<HTMLDivElement>(closeTooltip);
return (
<fieldset
diff --git a/src/components/molecules/forms/radio-group.tsx b/src/components/molecules/forms/radio-group.tsx
index 64bdaa0..134829f 100644
--- a/src/components/molecules/forms/radio-group.tsx
+++ b/src/components/molecules/forms/radio-group.tsx
@@ -2,7 +2,7 @@ import Fieldset, {
type FieldsetProps,
} from '@components/molecules/forms/fieldset';
import useStateChange from '@utils/hooks/use-state-change';
-import { ChangeEvent, FC, MouseEvent, SetStateAction, useState } from 'react';
+import { ChangeEvent, FC, MouseEvent, SetStateAction } from 'react';
import LabelledBooleanField, {
type LabelledBooleanFieldProps,
} from './labelled-boolean-field';
@@ -37,11 +37,15 @@ export type RadioGroupProps = Pick<
*/
initialChoice: string;
/**
- * The radio group kind. Default: regular.
+ * The radio group kind.
+ *
+ * @default 'regular
*/
kind?: 'regular' | 'toggle';
/**
- * The legend position. Default: inline.
+ * The legend position.
+ *
+ * @default 'inline'
*/
legendPosition?: FieldsetProps['legendPosition'];
/**
diff --git a/src/components/organisms/modals/settings-modal.module.scss b/src/components/organisms/modals/settings-modal.module.scss
deleted file mode 100644
index 70b786d..0000000
--- a/src/components/organisms/modals/settings-modal.module.scss
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
diff --git a/src/components/organisms/modals/settings-modal.tsx b/src/components/organisms/modals/settings-modal.tsx
index 34f5619..0ab6b7a 100644
--- a/src/components/organisms/modals/settings-modal.tsx
+++ b/src/components/organisms/modals/settings-modal.tsx
@@ -4,7 +4,6 @@ import dynamic from 'next/dynamic';
import { FC } from 'react';
import { useIntl } from 'react-intl';
import { type SettingsFormProps } from '../forms/settings-form';
-import styles from './settings-modal.module.scss';
const DynamicSettingsForm = dynamic(
() => import('@components/organisms/forms/settings-form'),
diff --git a/src/components/organisms/toolbar/toolbar.tsx b/src/components/organisms/toolbar/toolbar.tsx
index c18b8ea..50fc7f2 100644
--- a/src/components/organisms/toolbar/toolbar.tsx
+++ b/src/components/organisms/toolbar/toolbar.tsx
@@ -1,9 +1,9 @@
-import useClickOutside from '@utils/hooks/use-click-outside';
+import useOnClickOutside from '@utils/hooks/use-on-click-outside';
import useRouteChange from '@utils/hooks/use-route-change';
-import { FC, useRef, useState } from 'react';
+import { FC, useState } from 'react';
import MainNav, { type MainNavProps } from '../toolbar/main-nav';
import Search, { type SearchProps } from '../toolbar/search';
-import Settings, { SettingsProps } from '../toolbar/settings';
+import Settings, { type SettingsProps } from '../toolbar/settings';
import styles from './toolbar.module.scss';
export type ToolbarProps = Pick<SearchProps, 'searchPage'> &
@@ -33,16 +33,17 @@ const Toolbar: FC<ToolbarProps> = ({
const [isNavOpened, setIsNavOpened] = useState<boolean>(false);
const [isSearchOpened, setIsSearchOpened] = useState<boolean>(false);
const [isSettingsOpened, setIsSettingsOpened] = useState<boolean>(false);
- const mainNavRef = useRef<HTMLDivElement>(null);
- const searchRef = useRef<HTMLDivElement>(null);
- const settingsRef = useRef<HTMLDivElement>(null);
- useClickOutside(mainNavRef, () => isNavOpened && setIsNavOpened(false));
- useClickOutside(searchRef, () => isSearchOpened && setIsSearchOpened(false));
- useClickOutside(
- settingsRef,
+ const mainNavRef = useOnClickOutside<HTMLDivElement>(
+ () => isNavOpened && setIsNavOpened(false)
+ );
+ const searchRef = useOnClickOutside<HTMLDivElement>(
+ () => isSearchOpened && setIsSearchOpened(false)
+ );
+ const settingsRef = useOnClickOutside<HTMLDivElement>(
() => isSettingsOpened && setIsSettingsOpened(false)
);
+
useRouteChange(() => setIsSearchOpened(false));
return (
diff --git a/src/utils/hooks/use-click-outside.tsx b/src/utils/hooks/use-click-outside.tsx
deleted file mode 100644
index cead98b..0000000
--- a/src/utils/hooks/use-click-outside.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-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: (target: EventTarget) => 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(e.target);
- },
- [el, callback]
- );
-
- useEffect(() => {
- document.addEventListener('mousedown', handleEvent);
- document.addEventListener('focusin', handleEvent);
-
- return () => {
- document.removeEventListener('mousedown', handleEvent);
- document.removeEventListener('focusin', handleEvent);
- };
- }, [handleEvent]);
-};
-
-export default useClickOutside;
diff --git a/src/utils/hooks/use-on-click-outside.tsx b/src/utils/hooks/use-on-click-outside.tsx
new file mode 100644
index 0000000..04827b8
--- /dev/null
+++ b/src/utils/hooks/use-on-click-outside.tsx
@@ -0,0 +1,52 @@
+import { RefObject, useCallback, useEffect, useRef } from 'react';
+
+export type UseOnClickOutsideCallback = (target: Node) => void;
+
+/**
+ * Detect click/focus outside an element and fire a callback function.
+ *
+ * @param {UseOnClickOutsideCallback} callback - A callback function to fire.
+ * @param {boolean} useCapture - Define event propagation method. Default: true.
+ * @returns {RefObject} A React reference object.
+ */
+const useOnClickOutside = <T extends HTMLElement>(
+ callback: UseOnClickOutsideCallback,
+ useCapture: boolean = true
+): RefObject<T> => {
+ const ref = useRef<T | null>(null);
+
+ /**
+ * Check if the target is outside the ref.
+ *
+ * @param {Node} target - The event target.
+ * @returns {boolean | null} True if the target is outside the ref.
+ */
+ const isTargetOutside = (target: Node): boolean | null => {
+ return ref.current && !ref.current.contains(target);
+ };
+
+ /**
+ * Fire the callback if the event target is outside.
+ */
+ const handler = useCallback(
+ (e: MouseEvent | FocusEvent) => {
+ if (e.target && isTargetOutside(e.target as Node))
+ callback(e.target as Node);
+ },
+ [callback]
+ );
+
+ useEffect(() => {
+ document.addEventListener('click', handler, useCapture);
+ document.addEventListener('focusin', handler, useCapture);
+
+ return () => {
+ document.removeEventListener('click', handler);
+ document.removeEventListener('focusin', handler);
+ };
+ }, [handler, useCapture]);
+
+ return ref;
+};
+
+export default useOnClickOutside;