diff --git a/packages/eui/changelogs/upcoming/8233.md b/packages/eui/changelogs/upcoming/8233.md new file mode 100644 index 00000000000..0ce1c628acb --- /dev/null +++ b/packages/eui/changelogs/upcoming/8233.md @@ -0,0 +1,9 @@ +**Enhancement** + +- Updated `EuiModal` + - Introduced support for passing the onClose prop to child components dynamically. + - Enhanced flexibility by using React.Children.map and React.cloneElement to inject the onClose functionality into child elements where applicable. + +- Updated `EuiModalHeader` + - Added an accessible close button (EuiButtonIcon) integrated directly within the header. + - Introduced an optional onClose prop to enable parent-driven modal closing functionality. \ No newline at end of file diff --git a/packages/eui/i18ntokens.json b/packages/eui/i18ntokens.json index 9d869445eb4..9cc165d8ad1 100644 --- a/packages/eui/i18ntokens.json +++ b/packages/eui/i18ntokens.json @@ -5634,7 +5634,7 @@ "filepath": "src/components/markdown_editor/markdown_editor_toolbar.tsx" }, { - "token": "euiModal.closeModal", + "token": "euiModalHeader.closeModal", "defString": "Closes this modal window", "highlighting": "string", "loc": { @@ -5649,7 +5649,7 @@ "index": 3276 } }, - "filepath": "src/components/modal/modal.tsx" + "filepath": "src/components/modal/modal_header.tsx" }, { "token": "euiPaginationButtonArrow.firstPage", diff --git a/packages/eui/i18ntokens_changelog.json b/packages/eui/i18ntokens_changelog.json index ee7bc8861fc..fde3b8a4122 100644 --- a/packages/eui/i18ntokens_changelog.json +++ b/packages/eui/i18ntokens_changelog.json @@ -3810,7 +3810,7 @@ "value": "App navigation" }, { - "token": "euiModal.closeModal", + "token": "euiModalHeader.closeModal", "changeType": "added", "value": "Closes this modal window" }, diff --git a/packages/eui/src/components/modal/__snapshots__/confirm_modal.test.tsx.snap b/packages/eui/src/components/modal/__snapshots__/confirm_modal.test.tsx.snap index 8817c20828f..5311dc99aa1 100644 --- a/packages/eui/src/components/modal/__snapshots__/confirm_modal.test.tsx.snap +++ b/packages/eui/src/components/modal/__snapshots__/confirm_modal.test.tsx.snap @@ -18,18 +18,6 @@ exports[`EuiConfirmModal renders EuiConfirmModal 1`] = ` role="alertdialog" tabindex="0" > -
@@ -39,6 +27,18 @@ exports[`EuiConfirmModal renders EuiConfirmModal 1`] = ` > A confirmation modal +
-
@@ -139,6 +127,18 @@ exports[`EuiConfirmModal renders EuiConfirmModal without EuiModalBody, if empty > A confirmation modal +
diff --git a/packages/eui/src/components/modal/__snapshots__/modal_header.test.tsx.snap b/packages/eui/src/components/modal/__snapshots__/modal_header.test.tsx.snap index 840d7217717..41aebfe7956 100644 --- a/packages/eui/src/components/modal/__snapshots__/modal_header.test.tsx.snap +++ b/packages/eui/src/components/modal/__snapshots__/modal_header.test.tsx.snap @@ -7,5 +7,17 @@ exports[`EuiModalHeader is rendered 1`] = ` data-test-subj="test subject string" > children + `; diff --git a/packages/eui/src/components/modal/confirm_modal.tsx b/packages/eui/src/components/modal/confirm_modal.tsx index 1a3a848de95..bda25ad2eab 100644 --- a/packages/eui/src/components/modal/confirm_modal.tsx +++ b/packages/eui/src/components/modal/confirm_modal.tsx @@ -118,7 +118,7 @@ export const EuiConfirmModal: FunctionComponent = ({ if (title) { modalTitle = ( - + { Show confirm modal {isModalVisible && ( - + Title of modal diff --git a/packages/eui/src/components/modal/modal.stories.tsx b/packages/eui/src/components/modal/modal.stories.tsx index e1470db4440..33bdd862ac1 100644 --- a/packages/eui/src/components/modal/modal.stories.tsx +++ b/packages/eui/src/components/modal/modal.stories.tsx @@ -48,7 +48,7 @@ export const Playground: Story = { args: { children: ( <> - + Modal title @@ -66,7 +66,7 @@ export const ToggleExample: Story = { args: { children: ( <> - + Modal title @@ -94,7 +94,7 @@ export const InitialFocus: Story = { }; return ( - + Modal title diff --git a/packages/eui/src/components/modal/modal.styles.ts b/packages/eui/src/components/modal/modal.styles.ts index 2ae5e768fff..37d25dd1b9f 100644 --- a/packages/eui/src/components/modal/modal.styles.ts +++ b/packages/eui/src/components/modal/modal.styles.ts @@ -73,11 +73,5 @@ export const euiModalStyles = (euiThemeContext: UseEuiTheme) => { inset-block-start: auto; } `, - euiModal__closeIcon: css` - position: absolute; - inset-inline-end: ${euiTheme.size.xs}; - inset-block-start: ${euiTheme.size.xs}; - z-index: 3; - `, }; }; diff --git a/packages/eui/src/components/modal/modal.tsx b/packages/eui/src/components/modal/modal.tsx index 36fbdbc5c52..b1dda04624f 100644 --- a/packages/eui/src/components/modal/modal.tsx +++ b/packages/eui/src/components/modal/modal.tsx @@ -12,11 +12,8 @@ import classnames from 'classnames'; import { keys, useEuiTheme } from '../../services'; import { isDOMNode } from '../../utils'; -import { EuiButtonIcon } from '../button'; - import { EuiFocusTrap } from '../focus_trap'; import { EuiOverlayMask } from '../overlay_mask'; -import { EuiI18n } from '../i18n'; import { euiModalStyles } from './modal.styles'; @@ -51,6 +48,14 @@ export interface EuiModalProps extends HTMLAttributes { role?: 'dialog' | 'alertdialog'; } +type ChildWithOnClose = React.ReactElement<{ + onClose: ( + event?: + | React.KeyboardEvent + | React.MouseEvent + ) => void; +}>; + export const EuiModal: FunctionComponent = ({ className, children, @@ -89,7 +94,12 @@ export const EuiModal: FunctionComponent = ({ maxWidth === true && styles.defaultMaxWidth, ]; - const cssCloseIconStyles = [styles.euiModal__closeIcon]; + const enhancedChildren = React.Children.map(children, (child) => { + if (React.isValidElement(child)) { + return React.cloneElement(child as ChildWithOnClose, { onClose }); + } + return child; + }); return ( @@ -104,22 +114,7 @@ export const EuiModal: FunctionComponent = ({ aria-modal={true} {...rest} > - - {(closeModal: string) => ( - - )} - - {children} + {enhancedChildren} diff --git a/packages/eui/src/components/modal/modal_header.styles.ts b/packages/eui/src/components/modal/modal_header.styles.ts index 04b2945fd43..a7a6068d27a 100644 --- a/packages/eui/src/components/modal/modal_header.styles.ts +++ b/packages/eui/src/components/modal/modal_header.styles.ts @@ -30,5 +30,11 @@ export const euiModalHeaderStyles = (euiThemeContext: UseEuiTheme) => { padding-block-start: ${euiTheme.size.s}; } `, + euiModal__closeIcon: css` + position: absolute; + inset-inline-end: ${euiTheme.size.xs}; + inset-block-start: ${euiTheme.size.xs}; + z-index: 3; + `, }; }; diff --git a/packages/eui/src/components/modal/modal_header.test.tsx b/packages/eui/src/components/modal/modal_header.test.tsx index 53f1acf65e0..e7a37fb6ce7 100644 --- a/packages/eui/src/components/modal/modal_header.test.tsx +++ b/packages/eui/src/components/modal/modal_header.test.tsx @@ -13,12 +13,18 @@ import { render } from '../../test/rtl'; import { EuiModalHeader } from './modal_header'; +const onClose = jest.fn; + describe('EuiModalHeader', () => { - shouldRenderCustomStyles(children); + shouldRenderCustomStyles( + children + ); test('is rendered', () => { const { container } = render( - children + + children + ); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/packages/eui/src/components/modal/modal_header.tsx b/packages/eui/src/components/modal/modal_header.tsx index 8a75c153b87..e7ada06e5ac 100644 --- a/packages/eui/src/components/modal/modal_header.tsx +++ b/packages/eui/src/components/modal/modal_header.tsx @@ -13,13 +13,23 @@ import { CommonProps } from '../common'; import { useEuiTheme } from '../../services'; import { euiModalHeaderStyles } from './modal_header.styles'; +import { EuiButtonIcon } from '../button'; +import { EuiI18n } from '../i18n'; export type EuiModalHeaderProps = FunctionComponent< - HTMLAttributes & CommonProps + HTMLAttributes & + CommonProps & { + onClose?: ( + event: + | React.KeyboardEvent + | React.MouseEvent + ) => void; + } >; export const EuiModalHeader: EuiModalHeaderProps = ({ className, children, + onClose, ...rest }) => { const classes = classnames('euiModalHeader', className); @@ -27,10 +37,26 @@ export const EuiModalHeader: EuiModalHeaderProps = ({ const euiTheme = useEuiTheme(); const styles = euiModalHeaderStyles(euiTheme); const cssStyles = [styles.euiModalHeader]; + const cssCloseIconStyles = [styles.euiModal__closeIcon]; return (
{children} + + {(closeModal: string) => ( + + )} +
); };