From 1e997160e7edada2fbc1ee4b928cbfe9ac54a40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matuzal=C3=A9m=20Teles?= Date: Mon, 6 Feb 2023 17:07:18 -0600 Subject: [PATCH 1/4] fix(@clayui/modal): fixes error when closing the modal and not removing elements with inert When you have some component inside the Modal that is using the Overlay component with inert support when closing the modal it doesn't remove the inert attribute making the page inaccessible. --- packages/clay-modal/src/Modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clay-modal/src/Modal.tsx b/packages/clay-modal/src/Modal.tsx index 67937e9ff4..2faf2819db 100644 --- a/packages/clay-modal/src/Modal.tsx +++ b/packages/clay-modal/src/Modal.tsx @@ -4,7 +4,7 @@ */ import {ClayPortal, IPortalBaseProps} from '@clayui/shared'; -import {hideOthers} from 'aria-hidden'; +import {suppressOthers} from 'aria-hidden'; import classNames from 'classnames'; import React, {useEffect, useMemo, useRef} from 'react'; import warning from 'warning'; @@ -128,7 +128,7 @@ const ClayModal = ({ useEffect(() => { if (modalElementRef.current && show) { // Hide everything from ARIA except the Modal Body - return hideOthers(modalElementRef.current); + return suppressOthers(modalElementRef.current); } }, [show]); From 1a4d281ab9e0aed7275c9f9a27a9d2e014ed8ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matuzal=C3=A9m=20Teles?= Date: Mon, 6 Feb 2023 18:04:05 -0600 Subject: [PATCH 2/4] feat(@clayui/shared): adds the possibility to have overlay stacked There are use cases where you can have Overlay components stacked, for example DropDown with ColorPicker, Modal with any other Overlay. If the user clicks outside the overlay it will close all overlays, as well as any other interaction the overlay handles, like pressing esc. This implementation only handle with events if the overlay is the last element of the stack, allowing to have an overlay stacked. --- packages/clay-shared/src/Overlay.tsx | 38 ++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/clay-shared/src/Overlay.tsx b/packages/clay-shared/src/Overlay.tsx index 38eadb354f..94bcc97474 100644 --- a/packages/clay-shared/src/Overlay.tsx +++ b/packages/clay-shared/src/Overlay.tsx @@ -26,6 +26,8 @@ type Props = { triggerRef: React.RefObject; }; +const overlayStack: Array> = []; + /** * Overlay component is used for components like dialog and modal. * For example, Autocomplete, DatePicker, ColorPicker, DropDown are components @@ -46,6 +48,15 @@ export function Overlay({ }: Props) { const unsuppressCallbackRef = useRef(null); + const onHide = useCallback( + (action: 'escape' | 'blur') => { + if (overlayStack[overlayStack.length - 1] === menuRef) { + onClose(action); + } + }, + [onClose] + ); + useEvent( 'focus', useCallback( @@ -56,21 +67,24 @@ export function Overlay({ triggerRef.current && !triggerRef.current.contains(event.target as Node) ) { - onClose('blur'); + onHide('blur'); } }, - [onClose] + [onHide] ), isOpen, true, - [isOpen, onClose] + [isOpen, onHide] ); useEvent( 'keydown', useCallback( (event: KeyboardEvent) => { - if (event.key === Keys.Esc) { + if ( + event.key === Keys.Esc && + overlayStack[overlayStack.length - 1] === menuRef + ) { event.stopImmediatePropagation(); event.preventDefault(); @@ -99,12 +113,26 @@ export function Overlay({ useInteractOutside({ isDisabled: isOpen ? !isCloseOnInteractOutside : true, onInteract: () => { - onClose('blur'); + onHide('blur'); }, ref: portalRef ?? menuRef, triggerRef, }); + useEffect(() => { + if (isOpen) { + overlayStack.push(menuRef); + } + + return () => { + const index = overlayStack.indexOf(menuRef); + + if (index >= 0) { + overlayStack.splice(index, 1); + } + }; + }, [isOpen, menuRef]); + useEffect(() => { if (menuRef.current && isOpen) { const elements = suppress From d36353591e43dfe46841c48006298e8a3853fb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matuzal=C3=A9m=20Teles?= Date: Mon, 6 Feb 2023 18:08:22 -0600 Subject: [PATCH 3/4] fix(@clayui/shared): prevent closing the modal when clicking outside an Overlay rendered in the modal --- packages/clay-shared/src/Overlay.tsx | 6 ++++++ packages/clay-shared/src/useInteractOutside.tsx | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/packages/clay-shared/src/Overlay.tsx b/packages/clay-shared/src/Overlay.tsx index 94bcc97474..21e8301baf 100644 --- a/packages/clay-shared/src/Overlay.tsx +++ b/packages/clay-shared/src/Overlay.tsx @@ -115,6 +115,12 @@ export function Overlay({ onInteract: () => { onHide('blur'); }, + onInteractStart: (event) => { + if (overlayStack[overlayStack.length - 1] === menuRef && isModal) { + event.stopPropagation(); + event.preventDefault(); + } + }, ref: portalRef ?? menuRef, triggerRef, }); diff --git a/packages/clay-shared/src/useInteractOutside.tsx b/packages/clay-shared/src/useInteractOutside.tsx index b68aea24b4..e3b811be5b 100644 --- a/packages/clay-shared/src/useInteractOutside.tsx +++ b/packages/clay-shared/src/useInteractOutside.tsx @@ -8,6 +8,7 @@ import React, {useEffect, useRef} from 'react'; type Props = { isDisabled?: boolean; onInteract?: (event: Event) => void; + onInteractStart?: (event: Event) => void; ref: React.RefObject; triggerRef: React.RefObject; }; @@ -19,6 +20,7 @@ type Props = { export function useInteractOutside({ isDisabled = false, onInteract, + onInteractStart, ref, triggerRef, }: Props) { @@ -39,6 +41,9 @@ export function useInteractOutside({ const onPointerDown = (event: Event) => { if (isValidEvent(event, ref, triggerRef) && state.onInteract) { + if (onInteractStart) { + onInteractStart(event); + } state.isPointerDown = true; } }; From 653fc4e4f740198ca514d6c3d5dc9ef33ae168bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matuzal=C3=A9m=20Teles?= Date: Mon, 6 Feb 2023 18:11:22 -0600 Subject: [PATCH 4/4] chore(@clayui/modal): regen snapshot --- jest.config.js | 2 +- .../IncrementalInteractions.tsx.snap | 6 ++-- .../__tests__/__snapshots__/index.tsx.snap | 32 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/jest.config.js b/jest.config.js index 4949bcee45..3936b290c2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -195,7 +195,7 @@ module.exports = { }, './packages/clay-shared/src/': { branches: 20, - functions: 15, + functions: 14, lines: 36, statements: 39, }, diff --git a/packages/clay-modal/src/__tests__/__snapshots__/IncrementalInteractions.tsx.snap b/packages/clay-modal/src/__tests__/__snapshots__/IncrementalInteractions.tsx.snap index 375dfc8f31..8a4ef2a1da 100644 --- a/packages/clay-modal/src/__tests__/__snapshots__/IncrementalInteractions.tsx.snap +++ b/packages/clay-modal/src/__tests__/__snapshots__/IncrementalInteractions.tsx.snap @@ -24,7 +24,7 @@ exports[`ModalProvider -> IncrementalInteractions renders a modal when dispatchi