+
+
);
const maskWithClick = (
-
+ {
changeMaskWithClick(false);
@@ -39,16 +42,16 @@ export default () => {
return (
+ changeMaskWithClick(true)}>
+ Overlay with button
+
+ {
changeMask(true);
}}
>
- Overlay with onClick
-
-
- changeMaskWithClick(true)}>
- Overlay with button
+ Overlay with EuiFocusTrap click-to-close
{maskOpen ? modal : undefined}
{maskWithClickOpen ? maskWithClick : undefined}
diff --git a/src-docs/src/views/overlay_mask/overlay_mask_example.js b/src-docs/src/views/overlay_mask/overlay_mask_example.js
index ef6a91a4db5..48bd4220216 100644
--- a/src-docs/src/views/overlay_mask/overlay_mask_example.js
+++ b/src-docs/src/views/overlay_mask/overlay_mask_example.js
@@ -39,8 +39,11 @@ export const OverlayMaskExample = {
{' '}
to make before choosing to use an overlay. At the very least, you
must provide a visible button to close the overlay. You can also
- pass an onClick handler to handle closing the
- overlay.
+ nest an{' '}
+
+ EuiFocusTrap
+ {' '}
+ to handle closing the overlay.
>
),
diff --git a/src-docs/src/views/overlay_mask/overlay_mask_header.js b/src-docs/src/views/overlay_mask/overlay_mask_header.js
index 8cc6053ef44..bb171176fbd 100644
--- a/src-docs/src/views/overlay_mask/overlay_mask_header.js
+++ b/src-docs/src/views/overlay_mask/overlay_mask_header.js
@@ -19,7 +19,7 @@ export default () => {
if (flyOut) {
flyout = (
-
+
diff --git a/src-docs/src/views/overlay_mask/props.tsx b/src-docs/src/views/overlay_mask/props.tsx
index 06aaead948b..527de3f4dab 100644
--- a/src-docs/src/views/overlay_mask/props.tsx
+++ b/src-docs/src/views/overlay_mask/props.tsx
@@ -1,21 +1,7 @@
-import React, { FunctionComponent, ReactNode } from 'react';
+import React, { FunctionComponent } from 'react';
import { CommonProps } from '../../../../src/components/common';
+import { EuiOverlayMaskInterface } from '../../../../src/components/overlay_mask/overlay_mask';
-interface EuiOverlayMaskInterface extends CommonProps {
- /**
- * Function that applies to clicking the mask itself and not the children
- */
- onClick?: () => void;
- /**
- * ReactNode to render as this component's children
- */
- children?: ReactNode;
- /**
- * Should the mask visually sit above or below the EuiHeader (controlled by z-index)
- */
- headerZindexLocation?: 'above' | 'below';
-}
-
-export const EuiOverlayMaskProps: FunctionComponent = () => (
-
-);
+export const EuiOverlayMaskProps: FunctionComponent<
+ EuiOverlayMaskInterface & CommonProps
+> = () => ;
diff --git a/src/components/code/__snapshots__/code_block.test.tsx.snap b/src/components/code/__snapshots__/code_block.test.tsx.snap
index 1e6ffd75302..6fe263cbd09 100644
--- a/src/components/code/__snapshots__/code_block.test.tsx.snap
+++ b/src/components/code/__snapshots__/code_block.test.tsx.snap
@@ -108,6 +108,7 @@ exports[`EuiCodeBlock fullscreen displays content in fullscreen mode 1`] = `
Object {
"insert": [Function],
"inserted": Object {
+ "animation-ox4vyo": true,
"dsw53p-empty-text-hoverStyles-EuiButtonIcon": true,
},
"key": "css",
@@ -118,6 +119,27 @@ exports[`EuiCodeBlock fullscreen displays content in fullscreen mode 1`] = `
"_insertTag": [Function],
"before": null,
"container":
+
+
+
+
+
,
- "ctr": 3,
+ "ctr": 6,
"insertionPoint": undefined,
"isSpeedy": false,
"key": "css",
@@ -177,6 +220,27 @@ exports[`EuiCodeBlock fullscreen displays content in fullscreen mode 1`] = `
.css-dsw53p-empty-text-hoverStyles-EuiButtonIcon:hover{background-color:rgba(211,218,230,0.2);}
,
+ ,
+ ,
+ ,
],
},
}
diff --git a/src/components/image/image.test.tsx b/src/components/image/image.test.tsx
index 5853d05bb4d..d006f715b37 100644
--- a/src/components/image/image.test.tsx
+++ b/src/components/image/image.test.tsx
@@ -191,7 +191,11 @@ describe('EuiImage', () => {
expect(overlayMask).toBeFalsy();
});
- test('close using overlay mask', () => {
+ // Clicking the mask to close is now handled by EuiFocusTrap
+ // and we can't use Enzyme to test this behavior.
+ // A Cypress test exists in the EuiFocusTrap suite that
+ // sufficiently covers this behavior
+ test.skip('close using overlay mask', () => {
let overlayMask = document.querySelectorAll(
'[data-test-subj=fullScreenOverlayMask]'
);
diff --git a/src/components/image/image_fullscreen_wrapper.tsx b/src/components/image/image_fullscreen_wrapper.tsx
index 7d2887aa063..e31ed0d3960 100644
--- a/src/components/image/image_fullscreen_wrapper.tsx
+++ b/src/components/image/image_fullscreen_wrapper.tsx
@@ -65,11 +65,8 @@ export const EuiImageFullScreenWrapper: FunctionComponent
];
return (
-
-
+
+
<>
50) {
- $backgroundColor: $euiColorLightShade;
- }
-
- background: transparentize($backgroundColor, .2);
-}
-
-.euiBody-hasOverlayMask {
- overflow: hidden;
-}
-
-// Handling the z-index based on whether it should be displayed above or below the header
-.euiOverlayMask--aboveHeader {
- z-index: $euiZMask;
-}
-
-.euiOverlayMask--belowHeader {
- z-index: $euiZMaskBelowHeader;
-}
diff --git a/src/components/overlay_mask/overlay_mask.styles.ts b/src/components/overlay_mask/overlay_mask.styles.ts
new file mode 100644
index 00000000000..2590a4ac22a
--- /dev/null
+++ b/src/components/overlay_mask/overlay_mask.styles.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+import { logicalCSS, euiAnimFadeIn } from '../../global_styling';
+import { transparentize, UseEuiTheme } from '../../services';
+
+export const euiOverlayMaskStyles = ({ euiTheme }: UseEuiTheme) => ({
+ euiOverlayMask: css`
+ .euiOverlayMask {
+ position: fixed;
+ ${logicalCSS('top', 0)}
+ ${logicalCSS('left', 0)}
+ ${logicalCSS('right', 0)}
+ ${logicalCSS('bottom', 0)}
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ ${logicalCSS('padding-bottom', '10vh')};
+ animation: ${euiAnimFadeIn} ${euiTheme.animation.fast} ease-in;
+ background: ${transparentize(euiTheme.colors.ink, 0.5)};
+ }
+ `,
+ aboveHeader: css`
+ .euiOverlayMask {
+ z-index: ${euiTheme.levels.mask};
+ }
+ `,
+ belowHeader: css`
+ .euiOverlayMask {
+ z-index: ${euiTheme.levels.maskBelowHeader};
+ // TODO: use size variable when EuiHeader is converted
+ ${logicalCSS('top', `${euiTheme.base * 3}px`)};
+ }
+ `,
+});
+
+export const euiOverlayMaskBodyStyles = css`
+ body {
+ overflow: hidden;
+ }
+`;
diff --git a/src/components/overlay_mask/overlay_mask.tsx b/src/components/overlay_mask/overlay_mask.tsx
index 282b69b10f0..2f6fe007ca7 100644
--- a/src/components/overlay_mask/overlay_mask.tsx
+++ b/src/components/overlay_mask/overlay_mask.tsx
@@ -18,19 +18,19 @@ import React, {
ReactNode,
Ref,
useEffect,
- useRef,
useState,
} from 'react';
-import { createPortal } from 'react-dom';
import classNames from 'classnames';
+import { Global } from '@emotion/react';
import { CommonProps, keysOf } from '../common';
-import { useCombinedRefs } from '../../services';
+import { useCombinedRefs, useEuiTheme } from '../../services';
+import { EuiPortal } from '../portal';
+import {
+ euiOverlayMaskStyles,
+ euiOverlayMaskBodyStyles,
+} from './overlay_mask.styles';
export interface EuiOverlayMaskInterface {
- /**
- * Function that applies to clicking the mask itself and not the children
- */
- onClick?: () => void;
/**
* ReactNode to render as this component's content
*/
@@ -55,86 +55,49 @@ export type EuiOverlayMaskProps = CommonProps &
export const EuiOverlayMask: FunctionComponent = ({
className,
children,
- onClick,
headerZindexLocation = 'above',
maskRef,
css, // TODO: apply custom CSS-in-JS as a className
...rest
}) => {
- const overlayMaskNode = useRef();
- const combinedMaskRef = useCombinedRefs([overlayMaskNode, maskRef]);
- const [isPortalTargetReady, setIsPortalTargetReady] = useState(false);
-
- useEffect(() => {
- document.body.classList.add('euiBody-hasOverlayMask');
-
- return () => {
- document.body.classList.remove('euiBody-hasOverlayMask');
- };
- }, []);
+ const [overlayMaskNode, setOverlayMaskNode] = useState(
+ null
+ );
+ const combinedMaskRef = useCombinedRefs([
+ setOverlayMaskNode,
+ maskRef,
+ ]);
+ const euiTheme = useEuiTheme();
+ const styles = euiOverlayMaskStyles(euiTheme);
+ const cssStyles = [
+ styles.euiOverlayMask,
+ styles[`${headerZindexLocation}Header`],
+ ];
useEffect(() => {
- if (typeof document !== 'undefined') {
- combinedMaskRef(document.createElement('div'));
- }
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- const portalTarget = overlayMaskNode.current;
-
- if (portalTarget) {
- document.body.appendChild(portalTarget);
- }
-
- setIsPortalTargetReady(true);
-
- return () => {
- if (portalTarget) {
- document.body.removeChild(portalTarget);
- }
- };
- }, []);
-
- useEffect(() => {
- if (!overlayMaskNode.current) return;
+ if (!overlayMaskNode) return;
keysOf(rest).forEach((key) => {
if (typeof rest[key] !== 'string') {
throw new Error(
`Unhandled property type. EuiOverlayMask property ${key} is not a string.`
);
}
- if (overlayMaskNode.current) {
- overlayMaskNode.current.setAttribute(key, rest[key]!);
+ if (overlayMaskNode) {
+ overlayMaskNode.setAttribute(key, rest[key]!);
}
});
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- if (!overlayMaskNode.current) return;
- overlayMaskNode.current.className = classNames(
- 'euiOverlayMask',
- `euiOverlayMask--${headerZindexLocation}Header`,
- className
- );
- }, [className, headerZindexLocation]);
+ }, [overlayMaskNode]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
- const portalTarget = overlayMaskNode.current;
- if (!portalTarget || !onClick) return;
-
- const listener = (e: Event) => {
- if (e.target === portalTarget) {
- onClick();
- }
- };
- portalTarget.addEventListener('click', listener);
-
- return () => {
- portalTarget.removeEventListener('click', listener);
- };
- }, [onClick]);
-
- return isPortalTargetReady ? (
- <>{createPortal(children, overlayMaskNode.current!)}>
- ) : null;
+ if (!overlayMaskNode) return;
+ overlayMaskNode.className = classNames('euiOverlayMask', className);
+ }, [overlayMaskNode, className]);
+
+ return (
+
+
+
+ {children}
+
+ );
};
diff --git a/src/global_styling/index.ts b/src/global_styling/index.ts
index 5fc347eaf52..fbd28be2dff 100644
--- a/src/global_styling/index.ts
+++ b/src/global_styling/index.ts
@@ -10,3 +10,4 @@ export * from './reset/global_styles';
export * from './functions';
export * from './variables';
export * from './mixins';
+export * from './utility/animations';
diff --git a/src/global_styling/utility/animations.ts b/src/global_styling/utility/animations.ts
new file mode 100644
index 00000000000..bedd2330ecc
--- /dev/null
+++ b/src/global_styling/utility/animations.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { keyframes } from '@emotion/react';
+
+export const euiAnimFadeIn = keyframes`
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+`;
diff --git a/src/themes/amsterdam/overrides/_index.scss b/src/themes/amsterdam/overrides/_index.scss
index 38444a01edb..8566e195866 100644
--- a/src/themes/amsterdam/overrides/_index.scss
+++ b/src/themes/amsterdam/overrides/_index.scss
@@ -16,7 +16,6 @@
@import 'markdown_editor';
@import 'modal';
@import 'notification_badge';
-@import 'overlay_mask';
@import 'range';
@import 'range_draggable';
@import 'range_highlight';
diff --git a/src/themes/amsterdam/overrides/_overlay_mask.scss b/src/themes/amsterdam/overrides/_overlay_mask.scss
deleted file mode 100644
index 022a18082f6..00000000000
--- a/src/themes/amsterdam/overrides/_overlay_mask.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.euiOverlayMask {
- background: transparentize($euiColorInk, .5);
-}
\ No newline at end of file
diff --git a/upcoming_changelogs/6090.md b/upcoming_changelogs/6090.md
new file mode 100644
index 00000000000..a1c959ae7cb
--- /dev/null
+++ b/upcoming_changelogs/6090.md
@@ -0,0 +1,9 @@
+- Updated `EuiOverlayMask` to use `EuiPortal`
+
+**Breaking changes**
+
+- Removed `onClick` prop from `EuiOverlayMask`. Use a nested `EuiFocusTrap` instead.
+
+**CSS-in-JS conversions**
+
+- Converted `EuiOverlayMask` to Emotion