@@ -83,6 +83,7 @@ const ModalTour = props => {
8383 const [ isVisibleDelayed , setIsVisibleDelayed ] = useState ( false )
8484 const [ forceRefresh , setForceRefresh ] = useState ( 0 )
8585 const modalRef = useRef ( null )
86+ const glowElementRef = useRef ( null )
8687
8788 const {
8889 title,
@@ -99,15 +100,55 @@ const ModalTour = props => {
99100 nextEventTarget = null , // If provided, this is a selector for the element to trigger the next event if there is one.
100101 nextEvent = 'click' , // This is the event to listen for to trigger the next step.
101102 glowTarget = null , // If provided, this is a selector for the element to glow when the step is active.
103+ // eslint-disable-next-line no-unused-vars
104+ preStep = NOOP , // If provided, this is a function to run before the step is shown.
105+ // eslint-disable-next-line no-unused-vars
106+ postStep = NOOP , // If provided, this is a function to run after the step is shown.
102107 } = steps [ currentStep ]
103108
109+ // While the modal is visible, just keep on force refreshing the modal in an interval to make sure the modal is always in the correct position.
110+ useEffect ( ( ) => {
111+ let interval
112+ if ( isVisible ) {
113+ interval = setInterval ( ( ) => {
114+ setForceRefresh ( forceRefresh => forceRefresh + 1 )
115+ } , 300 )
116+ }
117+ return ( ) => clearInterval ( interval )
118+ } , [ isVisible , isVisibleDelayed ] )
119+
104120 // Create a stable function reference for the event listener
105121 const handleNextEvent = useCallback ( ( ) => {
106- setCurrentStep ( currentStep + 1 )
122+ setCurrentStep ( currentStep => {
123+ setTimeout ( ( ) => {
124+ steps [ currentStep ] ?. postStep ?. ( currentStep )
125+ } , 50 )
126+ const nextStep = currentStep + 1
127+ steps [ nextStep ] ?. preStep ?. ( nextStep )
128+ return nextStep
129+ } )
130+ setTimeout ( ( ) => {
131+ setForceRefresh ( forceRefresh => forceRefresh + 1 )
132+ } , 50 )
107133 setTimeout ( ( ) => {
108- setForceRefresh ( forceRefresh + 1 )
134+ setForceRefresh ( forceRefresh => forceRefresh + 1 )
135+ } , 350 )
136+ } , [ currentStep , steps ] )
137+
138+ const handleBackEvent = useCallback ( ( ) => {
139+ setCurrentStep ( currentStep => {
140+ // steps[ currentStep ]?.postStep?.( currentStep )
141+ const nextStep = currentStep - 1
142+ steps [ nextStep ] ?. preStep ?. ( nextStep )
143+ return nextStep
144+ } )
145+ setTimeout ( ( ) => {
146+ setForceRefresh ( forceRefresh => forceRefresh + 1 )
109147 } , 50 )
110- } , [ currentStep ] )
148+ setTimeout ( ( ) => {
149+ setForceRefresh ( forceRefresh => forceRefresh + 1 )
150+ } , 350 )
151+ } , [ currentStep , steps ] )
111152
112153 // Show modal after 1 second delay
113154 useEffect ( ( ) => {
@@ -116,7 +157,7 @@ const ModalTour = props => {
116157 setTimeout ( ( ) => {
117158 setIsVisibleDelayed ( true )
118159 } , 150 )
119- } , 1500 )
160+ } , 1050 )
120161
121162 return ( ) => clearTimeout ( timer )
122163 } , [ ] )
@@ -162,6 +203,22 @@ const ModalTour = props => {
162203 }
163204 } , [ currentStep , nextEventTarget , nextEvent , handleNextEvent ] )
164205
206+ // Create the glow element while this component is mounted.
207+ useEffect ( ( ) => {
208+ // Create the element.
209+ const element = document . createElement ( 'div' )
210+ element . className = `ugb-tour-modal__glow ugb-tour-modal__glow--hidden`
211+ document . body . appendChild ( element )
212+
213+ // Keep track of the element.
214+ glowElementRef . current = element
215+
216+ return ( ) => {
217+ glowElementRef . current = null
218+ element . remove ( )
219+ }
220+ } , [ ] )
221+
165222 // These are the X and Y offsets of the modal relative to the anchor. This will be
166223 const [ modalOffsetX , modalOffsetY ] = useMemo ( ( ) => {
167224 if ( ! modalRef . current ) {
@@ -179,6 +236,10 @@ const ModalTour = props => {
179236 // We have the modalRef.current which we can use to get the modal's bounding client rect.
180237 const anchorRect = document . querySelector ( anchor ) ?. getBoundingClientRect ( )
181238
239+ if ( ! anchorRect ) {
240+ return defaultOffset
241+ }
242+
182243 switch ( position ) {
183244 case 'left' :
184245 // Left, middle
@@ -216,21 +277,16 @@ const ModalTour = props => {
216277 : 'small'
217278
218279 // Create the element.
219- const element = document . createElement ( 'div' )
220- element . className = `ugb-tour-modal__glow ugb-tour-modal__glow--${ glowTargetSize } `
221- element . style . top = `${ targetRect . top - 8 } px`
222- element . style . left = `${ targetRect . left - 8 } px`
223- element . style . width = `${ targetRect . width + 16 } px`
224- element . style . height = `${ targetRect . height + 16 } px`
225- document . body . appendChild ( element )
226- }
227- }
228- // Remove the element when the component unmounts or the step changes.
229- return ( ) => {
230- if ( glowTarget ) {
231- const element = document . querySelector ( '.ugb-tour-modal__glow' )
232- element ?. remove ( )
280+ if ( glowElementRef . current ) {
281+ glowElementRef . current . className = `ugb-tour-modal__glow ugb-tour-modal__glow--${ glowTargetSize } `
282+ glowElementRef . current . style . top = `${ targetRect . top - 8 } px`
283+ glowElementRef . current . style . left = `${ targetRect . left - 8 } px`
284+ glowElementRef . current . style . width = `${ targetRect . width + 16 } px`
285+ glowElementRef . current . style . height = `${ targetRect . height + 16 } px`
286+ }
233287 }
288+ } else if ( glowElementRef . current ) {
289+ glowElementRef . current . className = `ugb-tour-modal__glow ugb-tour-modal__glow--hidden`
234290 }
235291 } , [ glowTarget , currentStep , isVisible , isVisibleDelayed , forceRefresh ] )
236292
@@ -270,10 +326,7 @@ const ModalTour = props => {
270326 < Button
271327 onClick = { ( ) => {
272328 ctaOnClick ( )
273- setCurrentStep ( currentStep + 1 )
274- setTimeout ( ( ) => {
275- setForceRefresh ( forceRefresh + 1 )
276- } , 50 )
329+ handleNextEvent ( )
277330 } }
278331 variant = "primary"
279332 className = "ugb-tour-modal__cta"
@@ -285,17 +338,11 @@ const ModalTour = props => {
285338 < Steps
286339 numSteps = { steps . length }
287340 currentStep = { currentStep }
288- onClickStep = { setCurrentStep }
289341 />
290342 { currentStep > 0 && (
291343 < Button
292344 variant = "tertiary"
293- onClick = { ( ) => {
294- setCurrentStep ( currentStep - 1 )
295- setTimeout ( ( ) => {
296- setForceRefresh ( forceRefresh + 1 )
297- } , 50 )
298- } }
345+ onClick = { handleBackEvent }
299346 >
300347 < Icon icon = { arrowLeft } size = { 20 } />
301348
@@ -337,10 +384,7 @@ const ModalTour = props => {
337384 }
338385 onClose ( )
339386 } else {
340- setCurrentStep ( currentStep + 1 )
341- setTimeout ( ( ) => {
342- setForceRefresh ( forceRefresh + 1 )
343- } , 100 )
387+ handleNextEvent ( )
344388 }
345389 } }
346390 >
@@ -364,7 +408,6 @@ const Steps = props => {
364408 const {
365409 numSteps = 3 ,
366410 currentStep = 0 ,
367- // onClickStep = NOOP,
368411 } = props
369412
370413 if ( numSteps === 1 ) {
@@ -382,7 +425,6 @@ const Steps = props => {
382425 return (
383426 < div
384427 className = { classes }
385- // onClick={ () => onClickStep( index ) }
386428 key = { index }
387429 />
388430 )
0 commit comments