Use data attributes for click outside instead of classNames (#12228)
We previously used classnames to exclude elements from the click outside listener. With this PR we can now use `data-click-outside-id` instead of `classNames` to target the elements we want to exclude from the click outside listener. We can also add `data-globally-prevent-click-outside` to a component to globally prevent triggering click outside listeners for other components. This attribute is especially useful for confirmation modals and snackbar items. Fixes #11785: https://github.com/user-attachments/assets/318baa7e-0f82-4e3a-a447-bf981328462d
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
type ClickOutsideListenerContextType = {
|
||||
excludeClassName: string | undefined;
|
||||
excludedClickOutsideId: string | undefined;
|
||||
};
|
||||
|
||||
export const ClickOutsideListenerContext =
|
||||
createContext<ClickOutsideListenerContextType>({
|
||||
excludeClassName: undefined,
|
||||
excludedClickOutsideId: undefined,
|
||||
});
|
||||
|
||||
@ -4,12 +4,13 @@ import { clickOutsideListenerMouseDownHappenedComponentState } from '@/ui/utilit
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const CLICK_OUTSIDE_DEBUG_MODE = false;
|
||||
|
||||
export type ClickOutsideListenerProps<T extends Element> = {
|
||||
refs: Array<React.RefObject<T>>;
|
||||
excludeClassNames?: string[];
|
||||
excludedClickOutsideIds?: string[];
|
||||
callback: (event: MouseEvent | TouchEvent) => void;
|
||||
listenerId: string;
|
||||
enabled?: boolean;
|
||||
@ -17,7 +18,7 @@ export type ClickOutsideListenerProps<T extends Element> = {
|
||||
|
||||
export const useListenClickOutside = <T extends Element>({
|
||||
refs,
|
||||
excludeClassNames,
|
||||
excludedClickOutsideIds,
|
||||
callback,
|
||||
listenerId,
|
||||
enabled = true,
|
||||
@ -90,12 +91,18 @@ export const useListenClickOutside = <T extends Element>({
|
||||
let currentElement: HTMLElement | null = clickedElement;
|
||||
|
||||
while (currentElement) {
|
||||
const currentClassList = currentElement.classList;
|
||||
const currentDataAttributes = currentElement.dataset;
|
||||
|
||||
const isGloballyExcluded =
|
||||
currentDataAttributes?.globallyPreventClickOutside === 'true';
|
||||
|
||||
const clickOutsideId = currentDataAttributes?.clickOutsideId;
|
||||
|
||||
isClickedOnExcluded =
|
||||
excludeClassNames?.some((className) =>
|
||||
currentClassList.contains(className),
|
||||
) ?? false;
|
||||
isGloballyExcluded ||
|
||||
(isDefined(clickOutsideId) &&
|
||||
isDefined(excludedClickOutsideIds) &&
|
||||
excludedClickOutsideIds.includes(clickOutsideId));
|
||||
|
||||
if (isClickedOnExcluded) {
|
||||
break;
|
||||
@ -140,7 +147,7 @@ export const useListenClickOutside = <T extends Element>({
|
||||
clickOutsideListenerIsMouseDownInsideState,
|
||||
clickOutsideListenerMouseDownHappenedState,
|
||||
refs,
|
||||
excludeClassNames,
|
||||
excludedClickOutsideIds,
|
||||
callback,
|
||||
listenerId,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user