1.7 KB63 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import * as stylex from '@stylexjs/stylex';
9import React, {useEffect, useRef} from 'react';
10import ReactDOM from 'react-dom';
11
12const styles = stylex.create({
13 root: {
14 position: 'absolute',
15 width: '100vw',
16 height: '100vh',
17 pointerEvents: 'none',
18 zIndex: 1000,
19 },
20});
21
22/**
23 * Render `children` as an overlay, in a container that uses absolute positioning.
24 * Suitable for tooltips, menus, and dragging elements.
25 */
26export function ViewportOverlay(props: {
27 children: React.ReactNode;
28 key?: React.Key | null;
29}): React.ReactPortal {
30 const {key, children} = props;
31 return ReactDOM.createPortal(
32 children as Parameters<
33 typeof ReactDOM.createPortal
34 >[0] /** ReactDOM's understanding of ReactNode seems wrong here */,
35 getRootContainer(),
36 key == null ? null : `overlay-${key}`,
37 ) as React.ReactPortal;
38}
39
40let cachedRoot: HTMLElement | undefined;
41const getRootContainer = (): HTMLElement => {
42 if (cachedRoot) {
43 // memoize since our root component won't change
44 return cachedRoot;
45 }
46 throw new Error(
47 'ViewportOverlayRoot not found. Make sure you render it at the root of the tree.',
48 );
49};
50
51export function ViewportOverlayRoot() {
52 const rootRef = useRef<HTMLDivElement | null>(null);
53 useEffect(() => {
54 if (rootRef.current) {
55 cachedRoot = rootRef.current;
56 }
57 return () => {
58 cachedRoot = undefined;
59 };
60 }, []);
61 return <div ref={rootRef} {...stylex.props(styles.root)} data-testid="viewport-overlay-root" />;
62}
63