11"use client" ;
22
3- import { useState , useEffect , useRef } from "react" ;
3+ import { useRef , useState } from "react" ;
4+
5+ import { useDemoAnimationLoop } from "./features-demo/useDemoAnimationLoop" ;
6+ import { useMeasureOnResize } from "./features-demo/useMeasureOnResize" ;
47import "./FeaturesDemo.css" ;
58import "./DeepSelectDemo.css" ;
69
7- function delay ( ms : number ) {
8- return new Promise ( ( r ) => setTimeout ( r , ms ) ) ;
9- }
10-
1110/* ─────────────────────────────────────────────────────────
1211 * ANIMATION STORYBOARD
1312 *
@@ -86,42 +85,37 @@ export function DeepSelectDemo() {
8685 } ;
8786 } ;
8887
89- useEffect ( ( ) => {
90- const timer = setTimeout ( measure , 100 ) ;
91- window . addEventListener ( "resize" , measure ) ;
92- return ( ) => {
93- clearTimeout ( timer ) ;
94- window . removeEventListener ( "resize" , measure ) ;
95- } ;
96- } , [ ] ) ;
88+ useMeasureOnResize ( { measure } ) ;
9789
98- useEffect ( ( ) => {
99- let cancelled = false ;
100- const feedbackText = "Add loading state" ;
90+ const feedbackText = "Add loading state" ;
10191
102- const run = async ( ) => {
103- // Reset
104- setCursorPos ( { x : 280 , y : 40 } ) ;
105- setHighlight ( { visible : false , mode : "normal" , rect : { x : 0 , y : 0 , w : 0 , h : 0 } } ) ;
106- setTooltip ( { visible : false , text : "" , type : "wrong" , x : 0 , y : 0 } ) ;
107- setShowPopup ( false ) ;
108- setTypedText ( "" ) ;
109- setShowMarker ( false ) ;
110- setOverlayFlash ( false ) ;
111- setActiveCaption ( "idle" ) ;
92+ const resetDemo = ( ) : void => {
93+ setCursorPos ( { x : 280 , y : 40 } ) ;
94+ setHighlight ( { visible : false , mode : "normal" , rect : { x : 0 , y : 0 , w : 0 , h : 0 } } ) ;
95+ setTooltip ( { visible : false , text : "" , type : "wrong" , x : 0 , y : 0 } ) ;
96+ setShowPopup ( false ) ;
97+ setTypedText ( "" ) ;
98+ setShowMarker ( false ) ;
99+ setOverlayFlash ( false ) ;
100+ setActiveCaption ( "idle" ) ;
101+ } ;
102+
103+ useDemoAnimationLoop (
104+ async ( { isCancelled, wait } ) => {
105+ resetDemo ( ) ;
112106
113- await delay ( 600 ) ;
114- if ( cancelled ) return ;
107+ if ( ! ( await wait ( 600 ) ) ) {
108+ return ;
109+ }
115110
116- // Re-measure before using positions (layout may have shifted)
117111 measure ( ) ;
118112 const card = cardPosRef . current ;
119113 const btn = btnPosRef . current ;
120114 setCursorPos ( { x : btn . x + btn . w / 2 , y : btn . y + btn . h / 2 } ) ;
121- await delay ( 400 ) ;
122- if ( cancelled ) return ;
115+ if ( ! ( await wait ( 400 ) ) ) {
116+ return ;
117+ }
123118
124- // Normal hover — highlights the whole card (the overlay intercepts)
125119 setOverlayFlash ( true ) ;
126120 setHighlight ( {
127121 visible : true ,
@@ -135,22 +129,22 @@ export function DeepSelectDemo() {
135129 x : card . x + card . w / 2 ,
136130 y : card . y - 10 ,
137131 } ) ;
138- await delay ( 1600 ) ;
139- if ( cancelled ) return ;
132+ if ( ! ( await wait ( 1600 ) ) ) {
133+ return ;
134+ }
140135
141- // Fade highlight + tooltip + overlay flash
142- setHighlight ( ( h ) => ( { ...h , visible : false } ) ) ;
143- setTooltip ( ( t ) => ( { ...t , visible : false } ) ) ;
136+ setHighlight ( ( currentHighlight ) => ( { ...currentHighlight , visible : false } ) ) ;
137+ setTooltip ( ( currentTooltip ) => ( { ...currentTooltip , visible : false } ) ) ;
144138 setOverlayFlash ( false ) ;
145- await delay ( 400 ) ;
146- if ( cancelled ) return ;
139+ if ( ! ( await wait ( 400 ) ) ) {
140+ return ;
141+ }
147142
148- // ⌘ beat — caption explains the feature
149143 setActiveCaption ( "cmd" ) ;
150- await delay ( 2000 ) ;
151- if ( cancelled ) return ;
144+ if ( ! ( await wait ( 2000 ) ) ) {
145+ return ;
146+ }
152147
153- // Pierce hover — highlights just the button
154148 setActiveCaption ( "correct" ) ;
155149 setHighlight ( {
156150 visible : true ,
@@ -164,61 +158,46 @@ export function DeepSelectDemo() {
164158 x : btn . x + btn . w / 2 ,
165159 y : btn . y - 10 ,
166160 } ) ;
167- await delay ( 1400 ) ;
168- if ( cancelled ) return ;
161+ if ( ! ( await wait ( 1400 ) ) ) {
162+ return ;
163+ }
169164
170- // Click — show popup
171165 setShowPopup ( true ) ;
172- await delay ( 300 ) ;
173- if ( cancelled ) return ;
166+ if ( ! ( await wait ( 300 ) ) ) {
167+ return ;
168+ }
174169
175- // Type feedback
176170 for ( let i = 0 ; i <= feedbackText . length ; i ++ ) {
177- if ( cancelled ) return ;
171+ if ( isCancelled ( ) ) {
172+ return ;
173+ }
174+
178175 setTypedText ( feedbackText . slice ( 0 , i ) ) ;
179- await delay ( 30 ) ;
176+ if ( ! ( await wait ( 30 ) ) ) {
177+ return ;
178+ }
179+ }
180+ if ( ! ( await wait ( 400 ) ) ) {
181+ return ;
180182 }
181- await delay ( 400 ) ;
182- if ( cancelled ) return ;
183183
184- // Close popup, show marker
185184 setShowPopup ( false ) ;
186- setHighlight ( ( h ) => ( { ...h , visible : false } ) ) ;
187- setTooltip ( ( t ) => ( { ...t , visible : false } ) ) ;
188- await delay ( 200 ) ;
189- if ( cancelled ) return ;
185+ setHighlight ( ( currentHighlight ) => ( { ...currentHighlight , visible : false } ) ) ;
186+ setTooltip ( ( currentTooltip ) => ( { ...currentTooltip , visible : false } ) ) ;
187+ if ( ! ( await wait ( 200 ) ) ) {
188+ return ;
189+ }
190190 setShowMarker ( true ) ;
191191
192- await delay ( 2200 ) ;
193- if ( cancelled ) return ;
194-
195- // Clean up for next loop
196- setShowMarker ( false ) ;
197- await delay ( 300 ) ;
198- } ;
199-
200- run ( ) ;
201- let interval = setInterval ( run , LOOP_INTERVAL ) ;
202-
203- const handleVisibility = ( ) => {
204- if ( document . visibilityState === "visible" ) {
205- cancelled = true ;
206- clearInterval ( interval ) ;
207- setTimeout ( ( ) => {
208- cancelled = false ;
209- run ( ) ;
210- interval = setInterval ( run , LOOP_INTERVAL ) ;
211- } , 100 ) ;
192+ if ( ! ( await wait ( 2200 ) ) ) {
193+ return ;
212194 }
213- } ;
214- document . addEventListener ( "visibilitychange" , handleVisibility ) ;
215195
216- return ( ) => {
217- cancelled = true ;
218- clearInterval ( interval ) ;
219- document . removeEventListener ( "visibilitychange" , handleVisibility ) ;
220- } ;
221- } , [ ] ) ;
196+ setShowMarker ( false ) ;
197+ await wait ( 300 ) ;
198+ } ,
199+ { intervalMs : LOOP_INTERVAL , onVisibilityRestart : resetDemo } ,
200+ ) ;
222201
223202 return (
224203 < div className = "fd-container" >
0 commit comments