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"
>
-
-
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) => (
+
+ )}
+
);
};