From 06850d26ebd6a126c93788a4f510aafac3f93c50 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 24 Feb 2026 16:56:58 -0500 Subject: [PATCH 1/3] feat: convert AlertModal to TypeScript Co-Authored-By: Claude Sonnet 4.6 --- src/Modal/{AlertModal.jsx => AlertModal.tsx} | 122 +++++++++++-------- src/index.ts | 3 +- 2 files changed, 69 insertions(+), 56 deletions(-) rename src/Modal/{AlertModal.jsx => AlertModal.tsx} (52%) diff --git a/src/Modal/AlertModal.jsx b/src/Modal/AlertModal.tsx similarity index 52% rename from src/Modal/AlertModal.jsx rename to src/Modal/AlertModal.tsx index 0fc42354e5..5889514282 100644 --- a/src/Modal/AlertModal.jsx +++ b/src/Modal/AlertModal.tsx @@ -1,21 +1,81 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { requiredWhenNot } from '../utils/propTypes'; import Icon from '../Icon'; import ModalDialog from './ModalDialog'; +interface AlertModalProps { + /** Specifies the content of the dialog */ + children: React.ReactNode; + /** The aria-label of the dialog */ + title: string; + /** Is the modal dialog open or closed */ + isOpen?: boolean; + /** Prevent clicking on the backdrop or pressing Esc to close the modal */ + isBlocking?: boolean; + /** Specifies whether the dialog box should contain 'x' icon button in the top right */ + hasCloseButton?: boolean; + /** A callback to close the modal dialog */ + onClose?: () => void; + /** Sizes determine the maximum width of the dialog box */ + size?: 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen'; + /** The visual style of the dialog box */ + variant?: 'default' | 'warning' | 'danger' | 'success'; + /** The label supplied to the close icon button if one is rendered */ + closeLabel?: string; + /** Specifies class name to append to the base element */ + className?: string; + /** + * Determines where a scrollbar should appear if a modal is too large for the + * viewport. When false, the ModalDialog.Body receives a scrollbar, when true + * the browser window itself receives the scrollbar. + */ + isFullscreenScroll?: boolean; + /** To show full screen view on mobile screens */ + isFullscreenOnMobile?: boolean; + /** Specifies whether overflow content inside the modal should be visible */ + isOverflowVisible?: boolean; + /** Specifies the z-index of the modal */ + zIndex?: number; + /** Specifies what should be displayed in the footer of the dialog box */ + footerNode?: React.ReactNode; + /** Icon that will be shown in the header of modal */ + icon?: React.ComponentType; +} + function AlertModal({ children, - footerNode, + footerNode = null, icon, - ...props -}) { + title, + isOpen = false, + isBlocking = false, + hasCloseButton = false, + onClose = () => {}, + size = 'md', + variant = 'default', + closeLabel = 'Close', + className, + isFullscreenScroll = false, + isFullscreenOnMobile, + isOverflowVisible = false, + zIndex, +}: AlertModalProps) { return ( @@ -26,7 +86,7 @@ function AlertModal({ className={classNames('pgn__alert-modal__title_icon')} /> )} - {props.title} + {title} {children} @@ -35,50 +95,4 @@ function AlertModal({ ); } -AlertModal.propTypes = { - children: PropTypes.node.isRequired, - /** The aria-label of the dialog */ - title: PropTypes.string.isRequired, - /** Is the modal dialog open or closed */ - isOpen: PropTypes.bool, - /** Prevent clicking on the backdrop or pressing Esc to close the modal */ - isBlocking: PropTypes.bool, - /** Specifies whether the dialog box should contain 'x' icon button in the top right */ - hasCloseButton: PropTypes.bool, - /** A callback to close the modal dialog */ - onClose: requiredWhenNot(PropTypes.func, 'isBlocking'), - /** Sizes determine the maximum width of the dialog box */ - size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl', 'fullscreen']), - /** The visual style of the dialog box */ - variant: PropTypes.oneOf(['default', 'warning', 'danger', 'success']), - /** The label supplied to the close icon button if one is rendered */ - closeLabel: PropTypes.string, - /** Specifies class name to append to the base element */ - className: PropTypes.string, - /** - * Determines where a scrollbar should appear if a modal is too large for the - * viewport. When false, the ModalDialog.Body receives a scrollbar, when true - * the browser window itself receives the scrollbar. - */ - isFullscreenScroll: PropTypes.bool, - /** Specifies what should be displayed in the footer of the dialog box */ - footerNode: PropTypes.node, - /** Icon that will be shown in the header of modal */ - icon: PropTypes.elementType, -}; - -AlertModal.defaultProps = { - isOpen: false, - isBlocking: false, - hasCloseButton: false, - onClose: () => {}, - size: 'md', - variant: 'default', - closeLabel: 'Close', - className: undefined, - isFullscreenScroll: false, - footerNode: null, - icon: undefined, -}; - export default AlertModal; diff --git a/src/index.ts b/src/index.ts index 7e11e26a8c..f5b56b7ab3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ // // // // // // // // // // // // // // // // // // // // // // // // // // // export { default as ActionRow } from './ActionRow'; export { default as Alert, ALERT_CLOSE_LABEL_TEXT } from './Alert'; +export { default as AlertModal } from './Modal/AlertModal'; export { default as Annotation } from './Annotation'; export { default as Avatar } from './Avatar'; export { default as AvatarButton } from './AvatarButton'; @@ -117,8 +118,6 @@ export { default as MarketingModal } from './Modal/MarketingModal'; // @ts-ignore: has yet to be converted to TypeScript export { default as StandardModal, STANDARD_MODAL_CLOSE_LABEL } from './Modal/StandardModal'; // @ts-ignore: has yet to be converted to TypeScript -export { default as AlertModal } from './Modal/AlertModal'; -// @ts-ignore: has yet to be converted to TypeScript export { default as ModalPopup } from './Modal/ModalPopup'; // @ts-ignore: has yet to be converted to TypeScript export { default as PopperElement } from './Modal/PopperElement'; From eb2ade8e93ed2c2814c07dabdbbd39f90b935392 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 25 Feb 2026 13:56:07 -0500 Subject: [PATCH 2/3] fix: restore props passthrough and tighten types in AlertModal - Use React.ComponentProps + Omit to restore the original ...props passthrough to ModalDialog - Redeclare onClose as optional (AlertModal defaults it to () => {}) - Redeclare isOverflowVisible as optional with default false (was absent from original propTypes, undefined is falsy) - Redeclare variant without 'dark' (intentionally excluded in original) Co-Authored-By: Claude Sonnet 4.6 --- src/Modal/AlertModal.tsx | 62 ++++++++++------------------------------ 1 file changed, 15 insertions(+), 47 deletions(-) diff --git a/src/Modal/AlertModal.tsx b/src/Modal/AlertModal.tsx index 5889514282..3b2b7f84ad 100644 --- a/src/Modal/AlertModal.tsx +++ b/src/Modal/AlertModal.tsx @@ -4,39 +4,21 @@ import classNames from 'classnames'; import Icon from '../Icon'; import ModalDialog from './ModalDialog'; -interface AlertModalProps { - /** Specifies the content of the dialog */ - children: React.ReactNode; - /** The aria-label of the dialog */ - title: string; - /** Is the modal dialog open or closed */ - isOpen?: boolean; - /** Prevent clicking on the backdrop or pressing Esc to close the modal */ - isBlocking?: boolean; - /** Specifies whether the dialog box should contain 'x' icon button in the top right */ - hasCloseButton?: boolean; +type ModalDialogProps = React.ComponentProps; + +// Extends all ModalDialog props, but omits certain props to re-declare them: +// - onClose: ModalDialog requires it, but AlertModal defaults it to () => {} +// - isOverflowVisible: required in ModalDialog but was absent from AlertModal's propTypes; +// undefined is falsy so defaulting to false matches the previous behavior +// - variant: ModalDialog allows 'dark' but AlertModal intentionally excludes it +// footerNode and icon are AlertModal-specific props not present on ModalDialog +interface AlertModalProps extends Omit { /** A callback to close the modal dialog */ onClose?: () => void; - /** Sizes determine the maximum width of the dialog box */ - size?: 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen'; - /** The visual style of the dialog box */ - variant?: 'default' | 'warning' | 'danger' | 'success'; - /** The label supplied to the close icon button if one is rendered */ - closeLabel?: string; - /** Specifies class name to append to the base element */ - className?: string; - /** - * Determines where a scrollbar should appear if a modal is too large for the - * viewport. When false, the ModalDialog.Body receives a scrollbar, when true - * the browser window itself receives the scrollbar. - */ - isFullscreenScroll?: boolean; - /** To show full screen view on mobile screens */ - isFullscreenOnMobile?: boolean; /** Specifies whether overflow content inside the modal should be visible */ isOverflowVisible?: boolean; - /** Specifies the z-index of the modal */ - zIndex?: number; + /** The visual style of the dialog box */ + variant?: 'default' | 'warning' | 'danger' | 'success'; /** Specifies what should be displayed in the footer of the dialog box */ footerNode?: React.ReactNode; /** Icon that will be shown in the header of modal */ @@ -48,33 +30,19 @@ function AlertModal({ footerNode = null, icon, title, - isOpen = false, - isBlocking = false, hasCloseButton = false, onClose = () => {}, - size = 'md', - variant = 'default', - closeLabel = 'Close', - className, - isFullscreenScroll = false, - isFullscreenOnMobile, isOverflowVisible = false, - zIndex, + className, + ...props }: AlertModalProps) { return ( From dea6dda0ca53f6914f74c86c45dd2cb7204ddeb2 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 25 Feb 2026 15:02:22 -0500 Subject: [PATCH 3/3] test(AlertModal): add tests for default prop branches Cover the onClose, footerNode, and isOverflowVisible default parameter branches, and the no-footer render path, to restore 100% coverage. Co-Authored-By: Claude Sonnet 4.6 --- src/Modal/tests/AlertModal.test.jsx | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Modal/tests/AlertModal.test.jsx b/src/Modal/tests/AlertModal.test.jsx index 88cbd3ae5c..03e88e49f6 100644 --- a/src/Modal/tests/AlertModal.test.jsx +++ b/src/Modal/tests/AlertModal.test.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { IntlProvider } from 'react-intl'; import AlertModal from '../AlertModal'; @@ -55,6 +56,30 @@ describe('', () => { expect(body).toBeInTheDocument(); }); + it('renders without optional props', () => { + render( + + + + + , + ); + expect(screen.getByText('The body of alert.')).toBeInTheDocument(); + expect(screen.queryByText('footer')).not.toBeInTheDocument(); + }); + + it('close button click invokes default no-op onClose without error', async () => { + render( + + + + + , + ); + await userEvent.click(screen.getByRole('button', { name: 'Close' })); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + describe('with variant prop', () => { it('renders warning variant', () => { render(