diff --git a/packages/react/src/components/ComposedModal/ComposedModal.stories.js b/packages/react/src/components/ComposedModal/ComposedModal.stories.js index 25b74d738095..7f47641fb028 100644 --- a/packages/react/src/components/ComposedModal/ComposedModal.stories.js +++ b/packages/react/src/components/ComposedModal/ComposedModal.stories.js @@ -41,99 +41,111 @@ export default { }; export const Default = () => { + const [open, setOpen] = useState(true); return ( - - - -

- 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. -

- - -
- -
+ <> + + 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 FullWidth = () => { + const [open, setOpen] = useState(true); return ( - - - - - - - - Column A - - - Column B - - - Column C - - - - - - Row 1 - Row 1 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc - dui magna, finibus id tortor sed, aliquet bibendum augue. Aenean - posuere sem vel euismod dignissim. Nulla ut cursus dolor. - Pellentesque vulputate nisl a porttitor interdum. - - - - Row 2 - Row 2 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc - dui magna, finibus id tortor sed, aliquet bibendum augue. Aenean - posuere sem vel euismod dignissim. Nulla ut cursus dolor. - Pellentesque vulputate nisl a porttitor interdum. - - - - Row 3 - Row 3 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc - dui magna, finibus id tortor sed, aliquet bibendum augue. Aenean - posuere sem vel euismod dignissim. Nulla ut cursus dolor. - Pellentesque vulputate nisl a porttitor interdum. - - - - - - - + <> + + setOpen(false)} isFullWidth> + + + + + + + Column A + + + Column B + + + Column C + + + + + + Row 1 + Row 1 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc + dui magna, finibus id tortor sed, aliquet bibendum augue. + Aenean posuere sem vel euismod dignissim. Nulla ut cursus + dolor. Pellentesque vulputate nisl a porttitor interdum. + + + + Row 2 + Row 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc + dui magna, finibus id tortor sed, aliquet bibendum augue. + Aenean posuere sem vel euismod dignissim. Nulla ut cursus + dolor. Pellentesque vulputate nisl a porttitor interdum. + + + + Row 3 + Row 3 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc + dui magna, finibus id tortor sed, aliquet bibendum augue. + Aenean posuere sem vel euismod dignissim. Nulla ut cursus + dolor. Pellentesque vulputate nisl a porttitor interdum. + + + + + + + + ); }; export const PassiveModal = () => { + const [open, setOpen] = useState(true); return ( - - - - + <> + + setOpen(false)}> + + + + ); }; @@ -201,75 +213,80 @@ export const WithStateManager = () => { }; export const WithScrollingContent = () => { + const [open, setOpen] = useState(true); return ( - - - -

- 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. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu - nibh odio. Nunc a consequat est, id porttitor sapien. Proin vitae leo - vitae orci tincidunt auctor eget eget libero. Ut tincidunt ultricies - fringilla. Aliquam erat volutpat. Aenean arcu odio, elementum vel - vehicula vitae, porttitor ac lorem. Sed viverra elit ac risus - tincidunt fermentum. Ut sollicitudin nibh id risus ornare ornare. - Etiam gravida orci ut lectus dictum, quis ultricies felis mollis. - Mauris nec commodo est, nec faucibus nibh. Nunc commodo ante quis - pretium consectetur. Ut ac nisl vitae mi mattis vulputate a at elit. - Nullam porttitor ex eget mi feugiat mattis. Nunc non sodales magna. - Proin ornare tellus quis hendrerit egestas. Donec pharetra leo nec - molestie sollicitudin.{' '} -

- -
- -
- - (item ? item.text : '')} - /> -
- -
+ <> + + 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. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus + eu nibh odio. Nunc a consequat est, id porttitor sapien. Proin vitae + leo vitae orci tincidunt auctor eget eget libero. Ut tincidunt + ultricies fringilla. Aliquam erat volutpat. Aenean arcu odio, + elementum vel vehicula vitae, porttitor ac lorem. Sed viverra elit + ac risus tincidunt fermentum. Ut sollicitudin nibh id risus ornare + ornare. Etiam gravida orci ut lectus dictum, quis ultricies felis + mollis. Mauris nec commodo est, nec faucibus nibh. Nunc commodo ante + quis pretium consectetur. Ut ac nisl vitae mi mattis vulputate a at + elit. Nullam porttitor ex eget mi feugiat mattis. Nunc non sodales + magna. Proin ornare tellus quis hendrerit egestas. Donec pharetra + leo nec molestie sollicitudin.{' '} +

+ +
+ +
+ + (item ? item.text : '')} + /> +
+ +
+ ); }; export const WithInlineLoading = () => { + const [open, setOpen] = useState(true); const [status, setStatus] = useState('inactive'); const [description, setDescription] = useState('Submitting...'); @@ -296,70 +313,77 @@ export const WithInlineLoading = () => { }; return ( - - - -

- 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. -

- + + 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 Playground = (args) => { + const [open, setOpen] = useState(true); return ( - - - -

- 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. -

- + + 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. +

+ + +
+ - -
- -
+ + ); }; diff --git a/packages/react/src/components/ComposedModal/ComposedModal.tsx b/packages/react/src/components/ComposedModal/ComposedModal.tsx index d77d54658c13..a5295638ceae 100644 --- a/packages/react/src/components/ComposedModal/ComposedModal.tsx +++ b/packages/react/src/components/ComposedModal/ComposedModal.tsx @@ -13,11 +13,12 @@ import { isElement } from 'react-is'; import PropTypes, { ReactNodeLike } from 'prop-types'; import { ModalHeader, type ModalHeaderProps } from './ModalHeader'; import { ModalFooter, type ModalFooterProps } from './ModalFooter'; +import debounce from 'lodash.debounce'; +import useIsomorphicEffect from '../../internal/useIsomorphicEffect'; +import mergeRefs from '../../tools/mergeRefs'; import cx from 'classnames'; - import toggleClass from '../../tools/toggleClass'; import requiredIfGivenPropIsTruthy from '../../prop-types/requiredIfGivenPropIsTruthy'; - import wrapFocus from '../../internal/wrapFocus'; import { usePrefix } from '../../internal/usePrefix'; import { keys, match } from '../../internal/keyboard'; @@ -50,23 +51,49 @@ export const ModalBody = React.forwardRef( ref ) { const prefix = usePrefix(); - const contentClass = cx( - `${prefix}--modal-content`, - hasForm && `${prefix}--modal-content--with-form`, - hasScrollingContent && `${prefix}--modal-scroll-content`, - customClassName - ); + const contentRef = useRef(null); + const [isScrollable, setIsScrollable] = useState(false); + const contentClass = cx({ + [`${prefix}--modal-content`]: true, + [`${prefix}--modal-content--with-form`]: hasForm, + [`${prefix}--modal-scroll-content`]: hasScrollingContent || isScrollable, + customClassName, + }); + + useIsomorphicEffect(() => { + if (contentRef.current) { + setIsScrollable( + contentRef.current.scrollHeight > contentRef.current.clientHeight + ); + } + + function handler() { + if (contentRef.current) { + setIsScrollable( + contentRef.current.scrollHeight > contentRef.current.clientHeight + ); + } + } + + const debouncedHandler = debounce(handler, 200); + window.addEventListener('resize', debouncedHandler); + return () => { + debouncedHandler.cancel(); + window.removeEventListener('resize', debouncedHandler); + }; + }, []); - const hasScrollingContentProps = hasScrollingContent - ? { tabIndex: 0, role: 'region' } - : {}; + const hasScrollingContentProps = + hasScrollingContent || isScrollable + ? { tabIndex: 0, role: 'region' } + : {}; return (
+ ref={mergeRefs(contentRef, ref)}> {children}
); diff --git a/packages/react/src/components/Modal/Modal.stories.js b/packages/react/src/components/Modal/Modal.stories.js index c3d5961c4244..fb60764d3560 100644 --- a/packages/react/src/components/Modal/Modal.stories.js +++ b/packages/react/src/components/Modal/Modal.stories.js @@ -35,128 +35,143 @@ export default { }; export const Default = () => { + const [open, setOpen] = useState(true); return ( - -

- 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. -

- - - - (item ? item.text : '')} - /> -
+ <> + + setOpen(false)} + modalHeading="Add a custom domain" + modalLabel="Account resources" + primaryButtonText="Add" + secondaryButtonText="Cancel"> +

+ 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. +

+ + + + (item ? item.text : '')} + /> +
+ ); }; export const FullWidth = () => { + const [open, setOpen] = useState(true); return ( - - - - - - Column A - - - Column B - - - Column C - - - - - - Row 1 - Row 1 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui - magna, finibus id tortor sed, aliquet bibendum augue. Aenean - posuere sem vel euismod dignissim. Nulla ut cursus dolor. - Pellentesque vulputate nisl a porttitor interdum. - - - - Row 2 - Row 2 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui - magna, finibus id tortor sed, aliquet bibendum augue. Aenean - posuere sem vel euismod dignissim. Nulla ut cursus dolor. - Pellentesque vulputate nisl a porttitor interdum. - - - - Row 3 - Row 3 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui - magna, finibus id tortor sed, aliquet bibendum augue. Aenean - posuere sem vel euismod dignissim. Nulla ut cursus dolor. - Pellentesque vulputate nisl a porttitor interdum. - - - - - + <> + + setOpen(false)} + isFullWidth + modalHeading="Full Width Modal" + modalLabel="An example of a modal with no padding" + primaryButtonText="Add" + secondaryButtonText="Cancel"> + + + + + Column A + + + Column B + + + Column C + + + + + + Row 1 + Row 1 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc + dui magna, finibus id tortor sed, aliquet bibendum augue. Aenean + posuere sem vel euismod dignissim. Nulla ut cursus dolor. + Pellentesque vulputate nisl a porttitor interdum. + + + + Row 2 + Row 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc + dui magna, finibus id tortor sed, aliquet bibendum augue. Aenean + posuere sem vel euismod dignissim. Nulla ut cursus dolor. + Pellentesque vulputate nisl a porttitor interdum. + + + + Row 3 + Row 3 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc + dui magna, finibus id tortor sed, aliquet bibendum augue. Aenean + posuere sem vel euismod dignissim. Nulla ut cursus dolor. + Pellentesque vulputate nisl a porttitor interdum. + + + + + + ); }; export const DangerModal = () => { + const [open, setOpen] = useState(true); return ( - + <> + + setOpen(false)} + danger + modalHeading="Are you sure you want to delete this custom domain?" + modalLabel="Account resources" + primaryButtonText="Delete" + secondaryButtonText="Cancel" + /> + ); }; @@ -199,156 +214,170 @@ const modalFooter = (numberOfButtons) => { }; export const WithScrollingContent = () => { + const [open, setOpen] = useState(true); return ( - -

- 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. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu - nibh odio. Nunc a consequat est, id porttitor sapien. Proin vitae leo - vitae orci tincidunt auctor eget eget libero. Ut tincidunt ultricies - fringilla. Aliquam erat volutpat. Aenean arcu odio, elementum vel - vehicula vitae, porttitor ac lorem. Sed viverra elit ac risus tincidunt - fermentum. Ut sollicitudin nibh id risus ornare ornare. Etiam gravida - orci ut lectus dictum, quis ultricies felis mollis. Mauris nec commodo - est, nec faucibus nibh. Nunc commodo ante quis pretium consectetur. Ut - ac nisl vitae mi mattis vulputate a at elit. Nullam porttitor ex eget mi - feugiat mattis. Nunc non sodales magna. Proin ornare tellus quis - hendrerit egestas. Donec pharetra leo nec molestie sollicitudin.{' '} -

- -
- -
- - (item ? item.text : '')} - /> -
+ <> + + setOpen(false)} + hasScrollingContent + modalHeading="Add a custom domain" + modalLabel="Account resources" + primaryButtonText="Add" + secondaryButtonText="Cancel"> +

+ 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. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu + nibh odio. Nunc a consequat est, id porttitor sapien. Proin vitae leo + vitae orci tincidunt auctor eget eget libero. Ut tincidunt ultricies + fringilla. Aliquam erat volutpat. Aenean arcu odio, elementum vel + vehicula vitae, porttitor ac lorem. Sed viverra elit ac risus + tincidunt fermentum. Ut sollicitudin nibh id risus ornare ornare. + Etiam gravida orci ut lectus dictum, quis ultricies felis mollis. + Mauris nec commodo est, nec faucibus nibh. Nunc commodo ante quis + pretium consectetur. Ut ac nisl vitae mi mattis vulputate a at elit. + Nullam porttitor ex eget mi feugiat mattis. Nunc non sodales magna. + Proin ornare tellus quis hendrerit egestas. Donec pharetra leo nec + molestie sollicitudin.{' '} +

+ +
+ +
+ + (item ? item.text : '')} + /> +
+ ); }; export const Playground = ({ numberOfButtons, ...args }) => { + const [open, setOpen] = useState(true); return ( - -

- 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. -

- - - {args.hasScrollingContent && ( - <> -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

-

Lorem ipsum

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id - accumsan augue. Phasellus consequat augue vitae tellus tincidunt - posuere. Curabitur justo urna, consectetur vel elit iaculis, - ultrices condimentum risus. Nulla facilisi. Etiam venenatis molestie - tellus. Quisque consectetur non risus eu rutrum.{' '} -

- - )} -
+ <> + + { + action(e); + setOpen(false); + }} + modalHeading="Add a custom domain" + primaryButtonText="Add" + secondaryButtonText="Cancel" + aria-label="Modal content" + open={open} + {...args} + {...modalFooter(numberOfButtons)}> +

+ 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. +

+ + + {args.hasScrollingContent && ( + <> +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+

Lorem ipsum

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id + accumsan augue. Phasellus consequat augue vitae tellus tincidunt + posuere. Curabitur justo urna, consectetur vel elit iaculis, + ultrices condimentum risus. Nulla facilisi. Etiam venenatis + molestie tellus. Quisque consectetur non risus eu rutrum.{' '} +

+ + )} +
+ ); }; @@ -390,9 +419,6 @@ Playground.argTypes = { onKeyDown: { action: 'onKeyDown', }, - onRequestClose: { - action: 'onRequestClose', - }, onRequestSubmit: { action: 'onRequestSubmit', }, @@ -491,11 +517,17 @@ export const WithStateManager = () => { }; export const PassiveModal = () => { + const [open, setOpen] = useState(true); return ( - + <> + + setOpen(false)} + passiveModal + modalHeading="You have been successfully signed out" + /> + ); }; @@ -525,18 +557,23 @@ export const WithInlineLoading = () => { setDescription('Deleting...'); }; + const [open, setOpen] = useState(true); return ( - + <> + + setOpen(false)} + danger + modalHeading="Are you sure you want to delete this custom domain?" + modalLabel="Account resources" + primaryButtonText="Delete" + secondaryButtonText="Cancel" + onRequestSubmit={submit} + loadingStatus={status} + loadingDescription={description} + onLoadingSuccess={resetStatus} + /> + ); }; diff --git a/packages/react/src/components/Modal/Modal.tsx b/packages/react/src/components/Modal/Modal.tsx index 0af0f7b8f80f..98381d541251 100644 --- a/packages/react/src/components/Modal/Modal.tsx +++ b/packages/react/src/components/Modal/Modal.tsx @@ -6,7 +6,7 @@ */ import PropTypes, { ReactNodeLike } from 'prop-types'; -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import classNames from 'classnames'; import { Close } from '@carbon/icons-react'; import toggleClass from '../../tools/toggleClass'; @@ -17,6 +17,8 @@ import requiredIfGivenPropIsTruthy from '../../prop-types/requiredIfGivenPropIsT import wrapFocus, { elementOrParentIsFloatingMenu, } from '../../internal/wrapFocus'; +import debounce from 'lodash.debounce'; +import useIsomorphicEffect from '../../internal/useIsomorphicEffect'; import setupGetInstanceId from '../../tools/setupGetInstanceId'; import { usePrefix } from '../../internal/usePrefix'; import { keys, match } from '../../internal/keyboard'; @@ -253,9 +255,11 @@ const Modal = React.forwardRef(function Modal( const prefix = usePrefix(); const button = useRef(null); const secondaryButton = useRef(); + const contentRef = useRef(null); const innerModal = useRef(null); const startTrap = useRef(null); const endTrap = useRef(null); + const [isScrollable, setIsScrollable] = useState(false); const modalInstanceId = `modal-${getInstanceId()}`; const modalLabelId = `${prefix}--modal-header__label--${modalInstanceId}`; const modalHeadingId = `${prefix}--modal-header__heading--${modalInstanceId}`; @@ -341,7 +345,7 @@ const Modal = React.forwardRef(function Modal( }); const contentClasses = classNames(`${prefix}--modal-content`, { - [`${prefix}--modal-scroll-content`]: hasScrollingContent, + [`${prefix}--modal-scroll-content`]: hasScrollingContent || isScrollable, }); const footerClasses = classNames(`${prefix}--modal-footer`, { @@ -358,14 +362,15 @@ const Modal = React.forwardRef(function Modal( modalLabelStr || ariaLabelProp || modalAriaLabel || modalHeadingStr; const getAriaLabelledBy = modalLabel ? modalLabelId : modalHeadingId; - const hasScrollingContentProps = hasScrollingContent - ? { - tabIndex: 0, - role: 'region', - 'aria-label': ariaLabel, - 'aria-labelledby': getAriaLabelledBy, - } - : {}; + const hasScrollingContentProps = + hasScrollingContent || isScrollable + ? { + tabIndex: 0, + role: 'region', + 'aria-label': ariaLabel, + 'aria-labelledby': getAriaLabelledBy, + } + : {}; const alertDialogProps: ReactAttr = {}; if (alert && passiveModal) { @@ -426,6 +431,29 @@ const Modal = React.forwardRef(function Modal( } }, [open, selectorPrimaryFocus, danger, prefix]); + useIsomorphicEffect(() => { + if (contentRef.current) { + setIsScrollable( + contentRef.current.scrollHeight > contentRef.current.clientHeight + ); + } + + function handler() { + if (contentRef.current) { + setIsScrollable( + contentRef.current.scrollHeight > contentRef.current.clientHeight + ); + } + } + + const debouncedHandler = debounce(handler, 200); + window.addEventListener('resize', debouncedHandler); + return () => { + debouncedHandler.cancel(); + window.removeEventListener('resize', debouncedHandler); + }; + }, []); + // Slug is always size `lg` let normalizedSlug; if (slug && slug['type']?.displayName === 'Slug') { @@ -483,6 +511,7 @@ const Modal = React.forwardRef(function Modal( {!passiveModal && modalButton}
diff --git a/packages/react/src/components/Slug/Slug-examples.stories.js b/packages/react/src/components/Slug/Slug-examples.stories.js index 8e8e92a506a6..9a56b41354b0 100644 --- a/packages/react/src/components/Slug/Slug-examples.stories.js +++ b/packages/react/src/components/Slug/Slug-examples.stories.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import Button from '../Button'; import Checkbox from '../Checkbox'; import CheckboxGroup from '../CheckboxGroup'; @@ -282,38 +282,42 @@ export const _ComposedModal = { '**Experimental**: Provide a `Slug` component to be rendered inside the component', }, }, - render: () => ( -
- - - -

- 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. -

- { + const [open, setOpen] = useState(true); // eslint-disable-line + return ( +
+ + setOpen(false)} slug={slug}> + + +

+ 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 _DatePicker = { @@ -382,34 +386,39 @@ export const _Modal = { '**Experimental**: Provide a `Slug` component to be rendered inside the component', }, }, - render: () => ( -
- -

- 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. -

- - -