Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiModal] Source order adjusted to annouce modal title before close #8233

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/eui/changelogs/upcoming/8233.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions packages/eui/i18ntokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -5649,7 +5649,7 @@
"index": 3276
}
},
"filepath": "src/components/modal/modal.tsx"
"filepath": "src/components/modal/modal_header.tsx"
},
{
"token": "euiPaginationButtonArrow.firstPage",
Expand Down
2 changes: 1 addition & 1 deletion packages/eui/i18ntokens_changelog.json
Original file line number Diff line number Diff line change
Expand Up @@ -3810,7 +3810,7 @@
"value": "App navigation"
},
{
"token": "euiModal.closeModal",
"token": "euiModalHeader.closeModal",
"changeType": "added",
"value": "Closes this modal window"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,6 @@ exports[`EuiConfirmModal renders EuiConfirmModal 1`] = `
role="alertdialog"
tabindex="0"
>
<button
aria-label="Closes this modal window"
class="euiButtonIcon euiModal__closeIcon emotion-euiButtonIcon-xs-empty-text-euiModal__closeIcon"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
<div
class="euiModalHeader emotion-euiModalHeader"
>
Expand All @@ -39,6 +27,18 @@ exports[`EuiConfirmModal renders EuiConfirmModal 1`] = `
>
A confirmation modal
</h1>
<button
aria-label="Closes this modal window"
class="euiButtonIcon euiModal__closeIcon emotion-euiButtonIcon-xs-empty-text-euiModal__closeIcon"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
</div>
<div
class="euiModalBody emotion-euiModalBody"
Expand Down Expand Up @@ -118,18 +118,6 @@ exports[`EuiConfirmModal renders EuiConfirmModal without EuiModalBody, if empty
role="alertdialog"
tabindex="0"
>
<button
aria-label="Closes this modal window"
class="euiButtonIcon euiModal__closeIcon emotion-euiButtonIcon-xs-empty-text-euiModal__closeIcon"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
<div
class="euiModalHeader emotion-euiModalHeader"
>
Expand All @@ -139,6 +127,18 @@ exports[`EuiConfirmModal renders EuiConfirmModal without EuiModalBody, if empty
>
A confirmation modal
</h1>
<button
aria-label="Closes this modal window"
class="euiButtonIcon euiModal__closeIcon emotion-euiButtonIcon-xs-empty-text-euiModal__closeIcon"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
</div>
<div
class="euiModalFooter emotion-euiModalFooter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,6 @@ exports[`EuiModal renders 1`] = `
role="dialog"
tabindex="0"
>
<button
aria-label="Closes this modal window"
class="euiButtonIcon euiModal__closeIcon emotion-euiButtonIcon-xs-empty-text-euiModal__closeIcon"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
children
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,17 @@ exports[`EuiModalHeader is rendered 1`] = `
data-test-subj="test subject string"
>
children
<button
aria-label="Closes this modal window"
class="euiButtonIcon euiModal__closeIcon emotion-euiButtonIcon-xs-empty-text-euiModal__closeIcon"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
</div>
`;
2 changes: 1 addition & 1 deletion packages/eui/src/components/modal/confirm_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const EuiConfirmModal: FunctionComponent<EuiConfirmModalProps> = ({

if (title) {
modalTitle = (
<EuiModalHeader>
<EuiModalHeader onClose={onCancel}>
<EuiModalHeaderTitle
data-test-subj="confirmModalTitleText"
{...titleProps}
Expand Down
2 changes: 1 addition & 1 deletion packages/eui/src/components/modal/modal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const Modal = ({ content }: { content?: ReactNode }) => {
<EuiButton onClick={showModal}>Show confirm modal</EuiButton>
{isModalVisible && (
<EuiModal {...modalProps}>
<EuiModalHeader>
<EuiModalHeader onClose={modalProps.onClose}>
<EuiModalHeaderTitle>Title of modal</EuiModalHeaderTitle>
</EuiModalHeader>

Expand Down
6 changes: 3 additions & 3 deletions packages/eui/src/components/modal/modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const Playground: Story = {
args: {
children: (
<>
<EuiModalHeader>
<EuiModalHeader onClose={action('onClose')}>
<EuiModalHeaderTitle>Modal title</EuiModalHeaderTitle>
</EuiModalHeader>

Expand All @@ -66,7 +66,7 @@ export const ToggleExample: Story = {
args: {
children: (
<>
<EuiModalHeader>
<EuiModalHeader onClose={action('onClose')}>
<EuiModalHeaderTitle>Modal title</EuiModalHeaderTitle>
</EuiModalHeader>

Expand Down Expand Up @@ -94,7 +94,7 @@ export const InitialFocus: Story = {
};
return (
<StatefulModal aria-labelledby="modalTitleId" {...args}>
<EuiModalHeader>
<EuiModalHeader onClose={action('onClose')}>
<EuiModalHeaderTitle id="modalTitleId">
Modal title
</EuiModalHeaderTitle>
Expand Down
6 changes: 0 additions & 6 deletions packages/eui/src/components/modal/modal.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`,
};
};
35 changes: 15 additions & 20 deletions packages/eui/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -51,6 +48,14 @@ export interface EuiModalProps extends HTMLAttributes<HTMLDivElement> {
role?: 'dialog' | 'alertdialog';
}

type ChildWithOnClose = React.ReactElement<{
onClose: (
event?:
| React.KeyboardEvent<HTMLDivElement>
| React.MouseEvent<HTMLButtonElement>
) => void;
}>;

export const EuiModal: FunctionComponent<EuiModalProps> = ({
className,
children,
Expand Down Expand Up @@ -89,7 +94,12 @@ export const EuiModal: FunctionComponent<EuiModalProps> = ({
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 (
<EuiOverlayMask>
Expand All @@ -104,22 +114,7 @@ export const EuiModal: FunctionComponent<EuiModalProps> = ({
aria-modal={true}
{...rest}
>
<EuiI18n
token="euiModal.closeModal"
default="Closes this modal window"
>
{(closeModal: string) => (
<EuiButtonIcon
iconType="cross"
onClick={onClose}
css={cssCloseIconStyles}
className="euiModal__closeIcon"
color="text"
aria-label={closeModal}
/>
)}
</EuiI18n>
{children}
{enhancedChildren}
</div>
</EuiFocusTrap>
</EuiOverlayMask>
Expand Down
6 changes: 6 additions & 0 deletions packages/eui/src/components/modal/modal_header.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`,
};
};
10 changes: 8 additions & 2 deletions packages/eui/src/components/modal/modal_header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ import { render } from '../../test/rtl';

import { EuiModalHeader } from './modal_header';

const onClose = jest.fn;

describe('EuiModalHeader', () => {
shouldRenderCustomStyles(<EuiModalHeader>children</EuiModalHeader>);
shouldRenderCustomStyles(
<EuiModalHeader onClose={onClose}>children</EuiModalHeader>
);

test('is rendered', () => {
const { container } = render(
<EuiModalHeader {...requiredProps}>children</EuiModalHeader>
<EuiModalHeader onClose={onClose} {...requiredProps}>
children
</EuiModalHeader>
);
expect(container.firstChild).toMatchSnapshot();
});
Expand Down
28 changes: 27 additions & 1 deletion packages/eui/src/components/modal/modal_header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,50 @@ 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<HTMLDivElement> & CommonProps
HTMLAttributes<HTMLDivElement> &
CommonProps & {
onClose?: (
event:
| React.KeyboardEvent<HTMLDivElement>
| React.MouseEvent<HTMLButtonElement>
) => void;
}
>;

export const EuiModalHeader: EuiModalHeaderProps = ({
className,
children,
onClose,
...rest
}) => {
const classes = classnames('euiModalHeader', className);

const euiTheme = useEuiTheme();
const styles = euiModalHeaderStyles(euiTheme);
const cssStyles = [styles.euiModalHeader];
const cssCloseIconStyles = [styles.euiModal__closeIcon];

return (
<div css={cssStyles} className={classes} {...rest}>
{children}
<EuiI18n
token="euiModalHeader.closeModal"
default="Closes this modal window"
>
{(closeModal: string) => (
<EuiButtonIcon
iconType="cross"
css={cssCloseIconStyles}
className="euiModal__closeIcon"
color="text"
onClick={onClose}
aria-label={closeModal}
/>
)}
</EuiI18n>
</div>
);
};