From e7690db03acac915fabd0d163ea81f2790ae54cd Mon Sep 17 00:00:00 2001 From: David Menendez Date: Wed, 26 Jul 2023 15:49:43 -0500 Subject: [PATCH 1/8] feat: nested modal support --- packages/react/src/components/Modal/Modal.js | 59 +++++----- .../src/components/Modal/Modal.stories.js | 102 +++++++++--------- .../styles/scss/components/modal/_modal.scss | 14 +-- 3 files changed, 93 insertions(+), 82 deletions(-) diff --git a/packages/react/src/components/Modal/Modal.js b/packages/react/src/components/Modal/Modal.js index 79aa018ce9e1..0da34fce7c7d 100644 --- a/packages/react/src/components/Modal/Modal.js +++ b/packages/react/src/components/Modal/Modal.js @@ -19,6 +19,7 @@ import wrapFocus, { import setupGetInstanceId from '../../tools/setupGetInstanceId'; import { usePrefix } from '../../internal/usePrefix'; import { keys, match } from '../../internal/keyboard'; +import { Portal } from '../Portal'; const getInstanceId = setupGetInstanceId(); @@ -283,32 +284,38 @@ const Modal = React.forwardRef(function Modal( ); return ( -
- {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} - - Focus sentinel - - {modalBody} - {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} - - Focus sentinel - -
+ +
+
+ {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} + + Focus sentinel + + {modalBody} + {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} + + Focus sentinel + +
+ ); }); diff --git a/packages/react/src/components/Modal/Modal.stories.js b/packages/react/src/components/Modal/Modal.stories.js index d75d48aaa391..024972e92b29 100644 --- a/packages/react/src/components/Modal/Modal.stories.js +++ b/packages/react/src/components/Modal/Modal.stories.js @@ -6,7 +6,6 @@ */ import React, { useState } from 'react'; -import ReactDOM from 'react-dom'; import { action } from '@storybook/addon-actions'; import Modal from './Modal'; import Button from '../Button'; @@ -356,58 +355,61 @@ Playground.argTypes = { }; export const WithStateManager = () => { - /** - * Simple state manager for modals. - */ - const ModalStateManager = ({ - renderLauncher: LauncherContent, - children: ModalContent, - }) => { - const [open, setOpen] = useState(false); - return ( - <> - {!ModalContent || typeof document === 'undefined' - ? null - : ReactDOM.createPortal( - , - document.body - )} - {LauncherContent && } - - ); - }; + const [open, setOpen] = useState(false); return ( - ( - - )}> - {({ open, setOpen }) => ( +
+ setOpen(false)}> +

+ Custom domains direct requests for your apps in this Cloud Foundry + organization to a URL that you own. A custom domain can be a shared + domain, a shared subdomain, or a shared domain and host. +

+ + +
+ +
+ ); +}; + +export const Nested = () => { + const [open1, setOpen1] = useState(false); + const [open2, setOpen2] = useState(false); + return ( +
+ setOpen1(false)}> setOpen(false)}> -

- Custom domains direct requests for your apps in this Cloud Foundry - organization to a URL that you own. A custom domain can be a shared - domain, a shared subdomain, or a shared domain and host. -

- - + id="modal2" + modalHeading="Modal Two" + passiveModal + open={open2} + onRequestClose={() => setOpen2(false)}> +

The second modal

- )} - + +
+ +
); }; diff --git a/packages/styles/scss/components/modal/_modal.scss b/packages/styles/scss/components/modal/_modal.scss index 969ab24116ee..ba8cb735aecd 100644 --- a/packages/styles/scss/components/modal/_modal.scss +++ b/packages/styles/scss/components/modal/_modal.scss @@ -26,15 +26,10 @@ .#{$prefix}--modal { position: fixed; z-index: z('modal'); - top: 0; - left: 0; display: flex; - width: 100vw; - height: 100vh; align-items: center; justify-content: center; - background-color: $overlay; - content: ''; + inset: 0; opacity: 0; transition: opacity $duration-moderate-02 motion(exit, expressive), visibility 0ms linear $duration-moderate-02; @@ -51,6 +46,13 @@ } } + .#{$prefix}--modal-background { + position: fixed; + z-index: -1; + background-color: $overlay; + inset: 0; + } + // V11: Question for design: do we have an updated tokens for fields that exist on `layer`? .#{$prefix}--pagination, .#{$prefix}--pagination__control-buttons, From 2cc440c3430b399777ad7d4894d626c7fc033a56 Mon Sep 17 00:00:00 2001 From: David Menendez Date: Tue, 29 Aug 2023 15:59:05 -0500 Subject: [PATCH 2/8] fix: revert portal change --- packages/react/src/components/Modal/Modal.js | 61 ++++---- .../src/components/Modal/Modal.stories.js | 142 ++++++++++++------ 2 files changed, 125 insertions(+), 78 deletions(-) diff --git a/packages/react/src/components/Modal/Modal.js b/packages/react/src/components/Modal/Modal.js index 3fd59a74b17c..7e5bb52ce5bc 100644 --- a/packages/react/src/components/Modal/Modal.js +++ b/packages/react/src/components/Modal/Modal.js @@ -19,7 +19,6 @@ import wrapFocus, { import setupGetInstanceId from '../../tools/setupGetInstanceId'; import { usePrefix } from '../../internal/usePrefix'; import { keys, match } from '../../internal/keyboard'; -import { Portal } from '../Portal'; const getInstanceId = setupGetInstanceId(); @@ -293,38 +292,36 @@ const Modal = React.forwardRef(function Modal( ); return ( - +
-
- {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} - - Focus sentinel - - {modalBody} - {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} - - Focus sentinel - -
- + className={`${prefix}--modal-background`} + onMouseDown={handleMousedown} + onBlur={handleBlur} + aria-hidden + /> + {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} + + Focus sentinel + + {modalBody} + {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} + + Focus sentinel + +
); }); diff --git a/packages/react/src/components/Modal/Modal.stories.js b/packages/react/src/components/Modal/Modal.stories.js index 39b913a854fe..5a9e2e66dc3c 100644 --- a/packages/react/src/components/Modal/Modal.stories.js +++ b/packages/react/src/components/Modal/Modal.stories.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import { action } from '@storybook/addon-actions'; import Modal from './Modal'; import Button from '../Button'; @@ -22,6 +22,7 @@ import { StructuredListRow, StructuredListCell, } from '../StructuredList'; +import { Portal } from '../Portal'; export default { title: 'Components/Modal', @@ -357,62 +358,111 @@ Playground.argTypes = { }, }; -export const WithStateManager = () => { +/** + * Simple state manager for modals. + */ +const ModalStateManager = ({ + renderLauncher: LauncherContent, + children: ModalContent, +}) => { const [open, setOpen] = useState(false); return ( -
- setOpen(false)}> -

- Custom domains direct requests for your apps in this Cloud Foundry - organization to a URL that you own. A custom domain can be a shared - domain, a shared subdomain, or a shared domain and host. -

- - -
- -
+ <> + + + + {LauncherContent && } + ); }; -export const Nested = () => { - const [open1, setOpen1] = useState(false); - const [open2, setOpen2] = useState(false); +export const WithStateManager = () => { + const button = useRef(); return ( -
- setOpen1(false)}> + ( + + )}> + {({ open, setOpen }) => ( + setOpen(false)}> +

+ Custom domains direct requests for your apps in this Cloud Foundry + organization to a URL that you own. A custom domain can be a shared + domain, a shared subdomain, or a shared domain and host. +

+ + +
+ )} +
+ ); +}; + +export const Nested = () => { + const button = useRef(); + + const ModalOne = ({ children }) => ( + ( + + )}> + {({ open, setOpen }) => ( + setOpen(false)}> + {children} + + )} + + ); + + const ModalTwo = ({ children }) => ( + ( + + )}> + {({ open, setOpen }) => ( setOpen2(false)}> -

The second modal

+ open={open} + onRequestClose={() => setOpen(false)}> + {children}
- -
- -
+ )} + + ); + + return ( + + + ); }; From 03dba6521020ef002e6fbafe460aca05ec3d4076 Mon Sep 17 00:00:00 2001 From: David Menendez Date: Wed, 6 Dec 2023 10:12:27 -0600 Subject: [PATCH 3/8] fix: revert modal background change and add escape --- packages/react/src/components/Modal/Modal.tsx | 12 +++++------- packages/styles/scss/components/modal/_modal.scss | 14 ++++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/react/src/components/Modal/Modal.tsx b/packages/react/src/components/Modal/Modal.tsx index 84b3bd323b18..74778a519735 100644 --- a/packages/react/src/components/Modal/Modal.tsx +++ b/packages/react/src/components/Modal/Modal.tsx @@ -268,6 +268,7 @@ const Modal = React.forwardRef(function Modal( } function handleKeyDown(evt: React.KeyboardEvent) { + evt.stopPropagation(); if (open) { if (match(evt, keys.Escape)) { onRequestClose(evt); @@ -284,6 +285,7 @@ const Modal = React.forwardRef(function Modal( function handleMousedown(evt: React.MouseEvent) { const target = evt.target as Node; + evt.stopPropagation(); if ( innerModal.current && !innerModal.current.contains(target) && @@ -522,13 +524,9 @@ const Modal = React.forwardRef(function Modal( className={modalClasses} ref={ref} role="presentation" - onKeyDown={handleKeyDown}> -
+ onKeyDown={handleKeyDown} + onMouseDown={handleMousedown} + onBlur={handleBlur}> {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} Date: Wed, 6 Dec 2023 10:16:11 -0600 Subject: [PATCH 4/8] fix: reorder props --- packages/react/src/components/Modal/Modal.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/Modal/Modal.tsx b/packages/react/src/components/Modal/Modal.tsx index 74778a519735..4b5ac23af9a1 100644 --- a/packages/react/src/components/Modal/Modal.tsx +++ b/packages/react/src/components/Modal/Modal.tsx @@ -521,12 +521,12 @@ const Modal = React.forwardRef(function Modal( return (
+ onBlur={handleBlur} + className={modalClasses} + role="presentation" + ref={ref}> {/* Non-translatable: Focus-wrap code makes this `` not actually read by screen readers */} Date: Wed, 31 Jan 2024 17:14:12 -0600 Subject: [PATCH 5/8] fix: add prevent default --- packages/react/src/components/ComposedModal/ComposedModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/ComposedModal/ComposedModal.tsx b/packages/react/src/components/ComposedModal/ComposedModal.tsx index 8a44f827ef73..fb57c213ef13 100644 --- a/packages/react/src/components/ComposedModal/ComposedModal.tsx +++ b/packages/react/src/components/ComposedModal/ComposedModal.tsx @@ -218,7 +218,7 @@ const ComposedModal = React.forwardRef( const startSentinel = useRef(null); const endSentinel = useRef(null); - // Kepp track of modal open/close state + // Keep track of modal open/close state // and propagate it to the document.body useEffect(() => { if (open !== wasOpen) { @@ -235,6 +235,7 @@ const ComposedModal = React.forwardRef( }, []); // eslint-disable-line react-hooks/exhaustive-deps function handleKeyDown(evt: KeyboardEvent) { + evt.preventDefault(); if (match(evt, keys.Escape)) { closeModal(evt); } @@ -242,6 +243,7 @@ const ComposedModal = React.forwardRef( onKeyDown?.(evt); } function handleMousedown(evt: MouseEvent) { + evt.preventDefault(); const isInside = innerModal.current?.contains(evt.target as Node); if (!isInside && !preventCloseOnClickOutside) { closeModal(evt); From 247e767490e158d6523df295d0a5a53fabe48cec Mon Sep 17 00:00:00 2001 From: David Menendez Date: Wed, 31 Jan 2024 17:23:33 -0600 Subject: [PATCH 6/8] fix: remove nested modal story --- .../src/components/Modal/Modal.stories.js | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/packages/react/src/components/Modal/Modal.stories.js b/packages/react/src/components/Modal/Modal.stories.js index 0f8656993e6b..31ff8020b204 100644 --- a/packages/react/src/components/Modal/Modal.stories.js +++ b/packages/react/src/components/Modal/Modal.stories.js @@ -486,56 +486,6 @@ export const WithStateManager = () => { ); }; -export const Nested = () => { - const button = useRef(); - - const ModalOne = ({ children }) => ( - ( - - )}> - {({ open, setOpen }) => ( - setOpen(false)}> - {children} - - )} - - ); - - const ModalTwo = ({ children }) => ( - ( - - )}> - {({ open, setOpen }) => ( - setOpen(false)}> - {children} - - )} - - ); - - return ( - - - - ); -}; - export const PassiveModal = () => { return ( Date: Wed, 31 Jan 2024 17:24:50 -0600 Subject: [PATCH 7/8] fix: revert additional changes to modal story code --- .../src/components/Modal/Modal.stories.js | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/react/src/components/Modal/Modal.stories.js b/packages/react/src/components/Modal/Modal.stories.js index 31ff8020b204..c3d5961c4244 100644 --- a/packages/react/src/components/Modal/Modal.stories.js +++ b/packages/react/src/components/Modal/Modal.stories.js @@ -6,6 +6,7 @@ */ import React, { useState, useRef } from 'react'; +import ReactDOM from 'react-dom'; import { action } from '@storybook/addon-actions'; import Modal from './Modal'; import Button from '../Button'; @@ -22,7 +23,6 @@ import { StructuredListRow, StructuredListCell, } from '../StructuredList'; -import { Portal } from '../Portal'; export default { title: 'Components/Modal', @@ -428,26 +428,30 @@ Playground.argTypes = { }, }; -/** - * Simple state manager for modals. - */ -const ModalStateManager = ({ - renderLauncher: LauncherContent, - children: ModalContent, -}) => { - const [open, setOpen] = useState(false); - return ( - <> - - - - {LauncherContent && } - - ); -}; - export const WithStateManager = () => { + /** + * Simple state manager for modals. + */ + const ModalStateManager = ({ + renderLauncher: LauncherContent, + children: ModalContent, + }) => { + const [open, setOpen] = useState(false); + return ( + <> + {!ModalContent || typeof document === 'undefined' + ? null + : ReactDOM.createPortal( + , + document.body + )} + {LauncherContent && } + + ); + }; + const button = useRef(); + return ( ( From 91a49f4c2b92d42e1a332c8649aa9d9d3a6700ef Mon Sep 17 00:00:00 2001 From: David Menendez Date: Tue, 20 Feb 2024 16:56:22 -0600 Subject: [PATCH 8/8] fix: stopPropagation not preventDefault --- packages/react/src/components/ComposedModal/ComposedModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/ComposedModal/ComposedModal.tsx b/packages/react/src/components/ComposedModal/ComposedModal.tsx index 9bd5cee1588c..883e6eb15601 100644 --- a/packages/react/src/components/ComposedModal/ComposedModal.tsx +++ b/packages/react/src/components/ComposedModal/ComposedModal.tsx @@ -259,7 +259,7 @@ const ComposedModal = React.forwardRef( }, []); // eslint-disable-line react-hooks/exhaustive-deps function handleKeyDown(evt: KeyboardEvent) { - evt.preventDefault(); + evt.stopPropagation(); if (match(evt, keys.Escape)) { closeModal(evt); } @@ -267,7 +267,7 @@ const ComposedModal = React.forwardRef( onKeyDown?.(evt); } function handleMousedown(evt: MouseEvent) { - evt.preventDefault(); + evt.stopPropagation(); const isInside = innerModal.current?.contains(evt.target as Node); if (!isInside && !preventCloseOnClickOutside) { closeModal(evt);