-
Notifications
You must be signed in to change notification settings - Fork 843
/
Copy pathoverlay_mask.tsx
123 lines (107 loc) Β· 3.26 KB
/
overlay_mask.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
* 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.
*/
/**
* NOTE: We can't test this component because Enzyme doesn't support rendering
* into portals.
*/
import React, {
FunctionComponent,
HTMLAttributes,
ReactNode,
useEffect,
useRef,
useState,
} from 'react';
import { createPortal } from 'react-dom';
import classNames from 'classnames';
import { CommonProps, keysOf } from '../common';
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
*/
children?: ReactNode;
/**
* Should the mask visually sit above or below the EuiHeader (controlled by z-index)
*/
headerZindexLocation?: 'above' | 'below';
}
export type EuiOverlayMaskProps = CommonProps &
Omit<
Partial<Record<keyof HTMLAttributes<HTMLDivElement>, string>>,
keyof EuiOverlayMaskInterface
> &
EuiOverlayMaskInterface;
export const EuiOverlayMask: FunctionComponent<EuiOverlayMaskProps> = ({
className,
children,
onClick,
headerZindexLocation = 'above',
...rest
}) => {
const overlayMaskNode = useRef<HTMLDivElement>(document.createElement('div'));
const [isPortalTargetReady, setIsPortalTargetReady] = useState(false);
useEffect(() => {
document.body.classList.add('euiBody-hasOverlayMask');
return () => {
document.body.classList.remove('euiBody-hasOverlayMask');
};
}, []);
useEffect(() => {
const portalTarget = overlayMaskNode.current;
if (document !== undefined) {
document.body.appendChild(overlayMaskNode.current);
}
setIsPortalTargetReady(true);
return () => {
if (portalTarget) {
document.body.removeChild(portalTarget);
}
};
}, []);
useEffect(() => {
if (!overlayMaskNode.current) return;
keysOf(rest).forEach((key) => {
if (typeof rest[key] !== 'string') {
throw new Error(
`Unhandled property type. EuiOverlayMask property ${key} is not a string.`
);
}
overlayMaskNode.current.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]);
useEffect(() => {
if (!overlayMaskNode.current || !onClick) return;
const portalTarget = overlayMaskNode.current;
const listener = (e: Event) => {
if (e.target === overlayMaskNode.current) {
onClick();
}
};
overlayMaskNode.current.addEventListener('click', listener);
return () => {
if (portalTarget && onClick) {
portalTarget.removeEventListener('click', listener);
}
};
}, [onClick]);
return isPortalTargetReady ? (
<>{createPortal(children, overlayMaskNode.current!)}</>
) : null;
};