1- import React , { useState , useEffect } from 'react' ;
2- import { useTransition , animated , config } from 'react-spring ' ;
1+ import React , { useState , useLayoutEffect } from 'react' ;
2+ import { motion } from 'framer-motion ' ;
33import track from '@codesandbox/common/lib/utils/analytics' ;
4- import { Container } from './elements' ;
4+ import Portal from '@codesandbox/common/lib/components/Portal' ;
5+ import { Container , ContentContainer } from './elements' ;
56
67interface IOverlayProps {
78 event : string ;
@@ -11,8 +12,11 @@ interface IOverlayProps {
1112 children : ( handleOpen : ( ) => void ) => React . ReactNode ;
1213 content : React . ComponentType ;
1314 noHeightAnimation ?: boolean ;
15+ width ?: number ;
1416}
1517
18+ const POPOVER_WIDTH = 390 ;
19+
1620const noop = ( ) => undefined ;
1721export const Overlay : React . FC < IOverlayProps > = ( {
1822 event,
@@ -22,33 +26,48 @@ export const Overlay: React.FC<IOverlayProps> = ({
2226 children,
2327 content : Content ,
2428 noHeightAnimation = true ,
29+ width = POPOVER_WIDTH ,
2530} ) => {
2631 const [ open , setOpen ] = useState ( isOpen === undefined ? false : isOpen ) ;
2732 const isControlled = isOpen !== undefined ;
2833 const openState = isControlled ? isOpen : open ;
34+ const element = React . useRef < HTMLDivElement > ( ) ;
35+ const bounds = React . useRef < {
36+ top : number ;
37+ left : number ;
38+ height : number ;
39+ width : number ;
40+ } > ( {
41+ top : 0 ,
42+ left : 0 ,
43+ width : 0 ,
44+ height : 0 ,
45+ } ) ;
2946
30- useEffect ( ( ) => {
31- const handleClick = ( e : MouseEvent ) => {
32- if ( ! e . defaultPrevented && openState ) {
33- if ( event ) {
34- track ( `Closed ${ event } ` ) ;
35- }
36- if ( isControlled ) {
37- if ( onClose ) {
38- onClose ( ) ;
39- }
40- } else {
41- setOpen ( false ) ;
42- }
43- }
47+ useLayoutEffect ( ( ) => {
48+ const posData = element . current . getBoundingClientRect ( ) ;
49+ bounds . current = {
50+ top : posData . top ,
51+ left : posData . left ,
52+ width : posData . width ,
53+ height : posData . height ,
4454 } ;
55+ } , [ ] ) ;
4556
46- document . addEventListener ( 'mousedown' , handleClick ) ;
47-
48- return ( ) => {
49- document . removeEventListener ( 'mousedown' , handleClick ) ;
50- } ;
51- } , [ isOpen , onClose , event , openState , isControlled ] ) ;
57+ const handleClick = ( ) => {
58+ if ( openState ) {
59+ if ( event ) {
60+ track ( `Closed ${ event } ` ) ;
61+ }
62+ if ( isControlled ) {
63+ if ( onClose ) {
64+ onClose ( ) ;
65+ }
66+ } else {
67+ setOpen ( false ) ;
68+ }
69+ }
70+ } ;
5271
5372 const handleOpen = ( ) => {
5473 if ( event ) {
@@ -63,35 +82,37 @@ export const Overlay: React.FC<IOverlayProps> = ({
6382 }
6483 } ;
6584
66- const transitions = useTransition ( openState , null , {
67- config : config . default ,
68- from : {
69- ...( noHeightAnimation ? { } : { height : 0 } ) ,
70- opacity : 0.6 ,
71- position : 'absolute' ,
72- top : 'calc(100% + 1rem)' ,
73- right : 0 ,
74- zIndex : 10 ,
75- overflow : 'hidden' ,
76- boxShadow : '0 3px 3px rgba(0, 0, 0, 0.3)' ,
77- } ,
78- enter : { ...( noHeightAnimation ? { } : { height : 'auto' } ) , opacity : 1 } ,
79- leave : { ...( noHeightAnimation ? { } : { height : 0 } ) , opacity : 0 } ,
80- } ) ;
81-
8285 return (
83- < Container onMouseDown = { e => e . preventDefault ( ) } >
86+ < Container onMouseDown = { e => e . stopPropagation ( ) } ref = { element } >
8487 { children ( handleOpen ) }
85- { transitions . map ( ( { item, props } , i ) =>
86- item ? (
87- // eslint-disable-next-line
88- < animated . div key = { i } style = { props } >
89- < Content />
90- </ animated . div >
91- ) : (
92- // eslint-disable-next-line
93- < animated . span key = { i } style = { props } />
94- )
88+
89+ { openState && (
90+ < Portal >
91+ < ContentContainer onClick = { handleClick } >
92+ < motion . div
93+ onClick = { e => e . stopPropagation ( ) }
94+ style = { {
95+ opacity : 0.6 ,
96+ position : 'absolute' ,
97+ zIndex : 10 ,
98+ overflow : 'hidden' ,
99+ boxShadow : '0 3px 3px rgba(0, 0, 0, 0.3)' ,
100+ height : 'auto' ,
101+ borderRadius : 4 ,
102+ top : bounds . current . top + bounds . current . height + 8 ,
103+ left : bounds . current . left + bounds . current . width - width * 0.75 ,
104+ width,
105+ } }
106+ transition = { {
107+ duration : 0.2 ,
108+ } }
109+ initial = { { y : - 5 } }
110+ animate = { { opacity : 1 , y : 0 } }
111+ >
112+ < Content />
113+ </ motion . div >
114+ </ ContentContainer >
115+ </ Portal >
95116 ) }
96117 </ Container >
97118 ) ;
0 commit comments