diff --git a/packages/web-react/src/components/Modal/ModalDialog.tsx b/packages/web-react/src/components/Modal/ModalDialog.tsx index 523c3e2a08..0b691228af 100644 --- a/packages/web-react/src/components/Modal/ModalDialog.tsx +++ b/packages/web-react/src/components/Modal/ModalDialog.tsx @@ -19,13 +19,14 @@ const ModalDialog = ( children, isDockedOnMobile, isExpandedOnMobile, + isScrollable, maxHeightFromTabletUp, preferredHeightOnMobile, preferredHeightFromTabletUp, ...restProps } = props; - const { classProps } = useModalStyleProps({ isDockedOnMobile, isExpandedOnMobile }); + const { classProps } = useModalStyleProps({ isDockedOnMobile, isExpandedOnMobile, isScrollable }); const { styleProps, props: otherProps } = useStyleProps(restProps); const customizedHeightStyle: CustomizedHeightCSSProperties = { diff --git a/packages/web-react/src/components/Modal/README.md b/packages/web-react/src/components/Modal/README.md index 0dbfb5ec32..2d53563e0e 100644 --- a/packages/web-react/src/components/Modal/README.md +++ b/packages/web-react/src/components/Modal/README.md @@ -114,6 +114,9 @@ This is useful for Modals with dynamic content, e.g. a list of items that can be ``` +👉 Please note the preferred height options are ignored when scrolling inside ModalDialog is +[turned off](#disable-scrolling-inside-modaldialog). + 👉 Please note the custom height values are considered **preferred:** Modal will not expand beyond the viewport height. ### Custom Max Height @@ -129,6 +132,8 @@ You can use the `maxHeightFromTabletUp` option to override the max height on tab ``` +👉 Please note the max height is ignored when scrolling inside ModalDialog is [turned off](#disable-scrolling-inside-modaldialog). + 👉 Please note the max height on mobile screens is currently not customizable. Let us know if you need this feature! 🙏 ### API @@ -139,6 +144,7 @@ You can use the `maxHeightFromTabletUp` option to override the max height on tab | `elementType` | [`article` \| `form`] | `article` | ✕ | ModalDialog element type | | `isDockedOnMobile` | `bool` | `false` | ✕ | [REQUIRES FEATURE FLAG](#feature-flag-uniform-appearance-on-all-breakpoints): Dock the ModalDialog to the bottom of the screen on mobile | | `isExpandedOnMobile` | `bool` | `false` | ✕ | ModalDialog shrinks to fit the height of its content | +| `isScrollable` | `bool` | `true` | ✕ | If the ModalDialog should be scrollable. If set to `false`, the dialog will not scroll and will expand to fit the content. | | `maxHeightFromTabletUp` | `string` | `null` | ✕ | Max height of the modal. Accepts any valid CSS value. | | `preferredHeightFromTabletUp` | `string` | `null` | ✕ | Preferred height of the modal on tablet and larger. Accepts any valid CSS value. | | `preferredHeightOnMobile` | `string` | `null` | ✕ | Preferred height of the modal on mobile. Accepts any valid CSS value. | @@ -284,6 +290,27 @@ takes over the responsibility for scrolling and provides visual overflow decorat ``` +### Disable Scrolling Inside ModalDialog + +Scrolling inside ModalDialog can be turned off by setting the `ModalDialog` prop `isScrollable` to `false`: + +```jsx + + + +``` + +This way, the ModalBody will expand to fit the height of its content and the whole ModalDialog will scroll in case the +content is longer than user's viewport. + +👉 Please note that this modifier class can produce unexpected results when used in combination with ScrollView. + +#### ⚠️ DEPRECATION NOTICE + +The `isScrollable` prop will be set to `false` by default in the next major release and the ModalDialog will be made +non-scrollable by default. It will be possible to re-enable the inside scrolling by setting the +`isScrollable` boolean prop. + ## Stacking Modals Multiple Modals can be open at the same time. That means, you can open a Modal from another Modal, and they will display diff --git a/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx b/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx index 523390f39c..171abaffec 100644 --- a/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx +++ b/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx @@ -1,4 +1,6 @@ import '@testing-library/jest-dom'; +import React from 'react'; +import { render } from '@testing-library/react'; import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest'; import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; import { restPropsTest } from '../../../../tests/providerTests/restPropsTest'; @@ -10,4 +12,54 @@ describe('ModalDialog', () => { stylePropsTest(ModalDialog); restPropsTest(ModalDialog, 'article'); + + it('should render children', () => { + const dom = render( + +
Test
+
, + ); + + expect(dom.container).toHaveTextContent('Test'); + }); + + it('should render with custom element type', () => { + const dom = render( + +
Test
+
, + ); + + expect(dom.container.querySelector('section')).toBeInTheDocument(); + }); + + it('should render docked on mobile', () => { + const dom = render( + +
Test
+
, + ); + + expect(dom.container.querySelector('.ModalDialog')).toHaveClass('ModalDialog--dockOnMobile'); + }); + + it('should render expanded on mobile', () => { + const dom = render( + +
Test
+
, + ); + + expect(dom.container.querySelector('.ModalDialog')).toHaveClass('ModalDialog--expandOnMobile'); + }); + + it('should render non scrollable', () => { + const dom = render( + +
Test
+
, + ); + + expect(dom.container.querySelector('.ModalDialog')).toHaveClass('ModalDialog--nonScrollable'); + }); }); diff --git a/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx b/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx index 7e8a95a966..052c0dac4e 100644 --- a/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx +++ b/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx @@ -4,12 +4,15 @@ import { Button, Modal, ModalBody, ModalDialog, ModalFooter, ModalHeader, Scroll const ModalScrollingLongContent = () => { const [isFirstOpen, setFirstOpen] = useState(false); const [isSecondOpen, setSecondOpen] = useState(false); + const [isThirdOpen, setThirdOpen] = useState(false); const toggleFirstModal = () => setFirstOpen(!isFirstOpen); const toggleSecondModal = () => setSecondOpen(!isSecondOpen); + const toggleThirdModal = () => setThirdOpen(!isThirdOpen); const handleFirstClose = () => setFirstOpen(false); const handleSecondClose = () => setSecondOpen(false); + const handleThirdClose = () => setThirdOpen(false); return ( <> @@ -131,6 +134,45 @@ const ModalScrollingLongContent = () => { + + + + + + + Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam mollitia + perferendis reprehenderit, voluptate. Cum delectus dicta + + +

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam mollitia + perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus perferendis + provident unde. Eveniet, iste, molestiae? +

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam mollitia + perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus perferendis + provident unde. Eveniet, iste, molestiae? +

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam mollitia + perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus perferendis + provident unde. Eveniet, iste, molestiae? +

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam mollitia + perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus perferendis + provident unde. Eveniet, iste, molestiae? +

+
+ + + + +
+
); }; diff --git a/packages/web-react/src/components/Modal/demo/ModalUniformModalOnMobile.tsx b/packages/web-react/src/components/Modal/demo/ModalUniformModalOnMobile.tsx index bc963545b8..9a91398e1d 100644 --- a/packages/web-react/src/components/Modal/demo/ModalUniformModalOnMobile.tsx +++ b/packages/web-react/src/components/Modal/demo/ModalUniformModalOnMobile.tsx @@ -1,33 +1,46 @@ import React, { ChangeEvent, useState } from 'react'; import { AlignmentX, AlignmentXDictionaryType, AlignmentY, AlignmentYDictionaryType } from '../../..'; -import { Button, Modal, ModalBody, ModalDialog, ModalFooter, ModalHeader, Radio } from '../..'; +import { Button, Checkbox, Modal, ModalBody, ModalDialog, ModalFooter, ModalHeader, Radio, Stack } from '../..'; const ModalDefault = () => { - const [isFirstOpen, setFirstOpen] = useState(false); - const [isSecondOpen, setSecondOpen] = useState(false); + const [isOpen, setOpen] = useState(false); const [modalAlign, setModalAlign] = useState(AlignmentY.CENTER); const [footerAlign, setFooterAlign] = useState(AlignmentX.RIGHT); + const [isDockedOnMobile, setIsDockedOnMobile] = useState(false); + const [isExpandedOnMobile, setIsExpandedOnMobile] = useState(false); + const [isScrollable, setIsScrollable] = useState(true); - const toggleFirstModal = () => setFirstOpen(!isFirstOpen); - const toggleSecondModal = () => setSecondOpen(!isSecondOpen); + const toggleModal = () => setOpen(!isOpen); - const handleFirstClose = () => setFirstOpen(false); - const handleSecondClose = () => setSecondOpen(false); + const handleClose = () => setOpen(false); const handleModalAlignChange = (event: ChangeEvent) => { setModalAlign(event.target.value as AlignmentYDictionaryType); }; const handleFooterAlignChange = (event: ChangeEvent) => { setFooterAlign(event.target.value as AlignmentXDictionaryType); }; + const handleDockedOnMobileChange = (event: ChangeEvent) => { + setIsDockedOnMobile(event.target.checked); + }; + const handleExpandedOnMobileChange = (event: ChangeEvent) => { + setIsExpandedOnMobile(event.target.checked); + }; + const handleScrollableChange = (event: ChangeEvent) => { + setIsScrollable(event.target.checked); + }; return ( <> {/* Set `display: contents` to enable parent stack layout. */}
- + - - + + Modal Title

@@ -35,7 +48,7 @@ const ModalDefault = () => { mollitia mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus provident unde. Eveniet, iste, molestiae?

-
+
Modal alignment:
{ onChange={handleModalAlignChange} /> -
+
Footer alignment (from tablet up):
{ onChange={handleFooterAlignChange} /> + + + + +
- - - -
-
- - - - - - Modal Title - -

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-
- - - + diff --git a/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx b/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx index 8155e1f6fd..9fc81d1419 100644 --- a/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx +++ b/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx @@ -17,6 +17,9 @@ const meta: Meta = { isExpandedOnMobile: { control: 'boolean', }, + isScrollable: { + control: 'boolean', + }, maxHeightFromTabletUp: { control: 'text', }, @@ -30,6 +33,7 @@ const meta: Meta = { args: { isDockedOnMobile: false, isExpandedOnMobile: false, + isScrollable: true, maxHeightFromTabletUp: '', preferredHeightOnMobile: '', preferredHeightFromTabletUp: '', diff --git a/packages/web-react/src/components/Modal/useModalStyleProps.ts b/packages/web-react/src/components/Modal/useModalStyleProps.ts index fed73f364b..ef6885ec00 100644 --- a/packages/web-react/src/components/Modal/useModalStyleProps.ts +++ b/packages/web-react/src/components/Modal/useModalStyleProps.ts @@ -7,6 +7,7 @@ export interface ModalStylesProps { footerAlignment?: AlignmentXDictionaryType; isDockedOnMobile?: boolean; isExpandedOnMobile?: boolean; + isScrollable?: boolean; modalAlignment?: AlignmentYDictionaryType; } @@ -26,19 +27,13 @@ export interface ModalStylesReturn { }; } -export function useModalStyleProps( - { - footerAlignment = AlignmentX.RIGHT, - isDockedOnMobile, - isExpandedOnMobile, - modalAlignment = AlignmentY.CENTER, - }: ModalStylesProps = { - footerAlignment: AlignmentX.RIGHT, - isDockedOnMobile: false, - isExpandedOnMobile: false, - modalAlignment: AlignmentX.CENTER, - }, -): ModalStylesReturn { +export function useModalStyleProps({ + footerAlignment = AlignmentX.RIGHT, + isDockedOnMobile = false, + isExpandedOnMobile = false, + isScrollable = true, + modalAlignment = AlignmentY.CENTER, +}: ModalStylesProps = {}): ModalStylesReturn { const modalClass = useClassNamePrefix('Modal'); const modalAlignClasses = { top: `${modalClass}--top`, @@ -48,6 +43,7 @@ export function useModalStyleProps( const modalDialogClass = `${modalClass}Dialog`; const modalDialogDockedOnMobileClass = `${modalDialogClass}--dockOnMobile`; const modalDialogExpandedOnMobileClass = `${modalDialogClass}--expandOnMobile`; + const modalDialogNonScrollableClass = `${modalDialogClass}--nonScrollable`; const modalHeaderClass = `${modalClass}Header`; const modalTitleClass = `${modalHeaderClass}__title`; const modalBodyClass = `${modalClass}Body`; @@ -64,6 +60,7 @@ export function useModalStyleProps( dialog: classNames(modalDialogClass, { [modalDialogDockedOnMobileClass]: isDockedOnMobile, [modalDialogExpandedOnMobileClass]: isExpandedOnMobile, + [modalDialogNonScrollableClass]: !isScrollable, }), title: modalTitleClass, header: modalHeaderClass, diff --git a/packages/web-react/src/types/modal.ts b/packages/web-react/src/types/modal.ts index 94341caea3..b27c772c15 100644 --- a/packages/web-react/src/types/modal.ts +++ b/packages/web-react/src/types/modal.ts @@ -28,6 +28,7 @@ export type ModalDialogBaseProps elementType?: E; isDockedOnMobile?: boolean; isExpandedOnMobile?: boolean; + isScrollable?: boolean; } & ChildrenProps & StyleProps;