| 1 |
/** |
| 2 |
* WordPress dependencies |
| 3 |
*/ |
| 4 |
import { Spinner } from '@wordpress/components'; |
| 5 |
import { useEffect, useState } from '@wordpress/element'; |
| 6 |
import { __ } from '@wordpress/i18n'; |
| 7 |
|
| 8 |
/** |
| 9 |
* TypewriterText component that animates text with a typewriter effect. |
| 10 |
* |
| 11 |
* @since 3.19.0 |
| 12 |
* |
| 13 |
* @param {Object} props The component's props. |
| 14 |
* @param {string} props.message The message to display with typewriter effect. |
| 15 |
*/ |
| 16 |
const TypewriterText = ( { message }: { message: string } ): JSX.Element => { |
| 17 |
const [ displayText, setDisplayText ] = useState( '' ); |
| 18 |
const [ currentIndex, setCurrentIndex ] = useState( 0 ); |
| 19 |
const [ isTyping, setIsTyping ] = useState( true ); |
| 20 |
|
| 21 |
useEffect( () => { |
| 22 |
// Reset when message changes |
| 23 |
setDisplayText( '' ); |
| 24 |
setCurrentIndex( 0 ); |
| 25 |
setIsTyping( true ); |
| 26 |
}, [ message ] ); |
| 27 |
|
| 28 |
useEffect( () => { |
| 29 |
if ( ! isTyping ) { |
| 30 |
return; |
| 31 |
} |
| 32 |
|
| 33 |
if ( currentIndex < message.length ) { |
| 34 |
// Still typing the message. |
| 35 |
const typingTimeout = setTimeout( () => { |
| 36 |
setDisplayText( ( prev ) => prev + message.charAt( currentIndex ) ); |
| 37 |
setCurrentIndex( ( prev ) => prev + 1 ); |
| 38 |
}, 50 ); // Typing speed. |
| 39 |
|
| 40 |
return () => clearTimeout( typingTimeout ); |
| 41 |
} |
| 42 |
}, [ currentIndex, isTyping, message ] ); |
| 43 |
|
| 44 |
return ( |
| 45 |
<div className="wp-parsely-typewriter-text"> |
| 46 |
{ displayText } |
| 47 |
</div> |
| 48 |
); |
| 49 |
}; |
| 50 |
|
| 51 |
/** |
| 52 |
* Defines the props structure for Loading. |
| 53 |
* |
| 54 |
* @since 3.19.0 |
| 55 |
*/ |
| 56 |
type LoadingProps = { |
| 57 |
className?: string; |
| 58 |
loading?: boolean; |
| 59 |
showSpinner?: boolean; |
| 60 |
messages?: string[]; |
| 61 |
randomOrder?: boolean; |
| 62 |
children?: React.ReactNode; |
| 63 |
typewriter: boolean; |
| 64 |
}; |
| 65 |
|
| 66 |
/** |
| 67 |
* Loading component that displays a spinner and a message. |
| 68 |
* |
| 69 |
* @since 3.19.0 |
| 70 |
* |
| 71 |
* @param {LoadingProps} props The component's props. |
| 72 |
*/ |
| 73 |
export const Loading = ( { |
| 74 |
className = '', |
| 75 |
loading = true, |
| 76 |
showSpinner = true, |
| 77 |
randomOrder = false, |
| 78 |
messages = [ __( 'Loading…', 'wp-parsely' ) ], |
| 79 |
children, |
| 80 |
typewriter = false, |
| 81 |
}: LoadingProps ): JSX.Element => { |
| 82 |
const [ currentMessage, setCurrentMessage ] = useState<string>( '' ); |
| 83 |
const [ currentIndex, setCurrentIndex ] = useState( 0 ); |
| 84 |
|
| 85 |
useEffect( () => { |
| 86 |
if ( ! loading ) { |
| 87 |
return; |
| 88 |
} |
| 89 |
|
| 90 |
if ( '' === currentMessage ) { |
| 91 |
setCurrentMessage( messages[ randomOrder ? Math.floor( Math.random() * messages.length ) : 0 ] ); |
| 92 |
} |
| 93 |
|
| 94 |
const updateMessage = () => { |
| 95 |
if ( randomOrder ) { |
| 96 |
// Pick a random message different from the current one. |
| 97 |
let nextIndex; |
| 98 |
do { |
| 99 |
nextIndex = Math.floor( Math.random() * messages.length ); |
| 100 |
} while ( messages.length > 1 && nextIndex === currentIndex ); |
| 101 |
setCurrentIndex( nextIndex ); |
| 102 |
setCurrentMessage( messages[ nextIndex ] ); |
| 103 |
} else { |
| 104 |
// Move to next message sequentially. |
| 105 |
const nextIndex = ( currentIndex + 1 ) % messages.length; |
| 106 |
setCurrentIndex( nextIndex ); |
| 107 |
setCurrentMessage( messages[ nextIndex ] ); |
| 108 |
} |
| 109 |
}; |
| 110 |
|
| 111 |
// Calculate pause duration based on message length (100ms per character). |
| 112 |
const pauseDuration = currentMessage.length * 100; |
| 113 |
|
| 114 |
const intervalId = setInterval( updateMessage, pauseDuration ); |
| 115 |
|
| 116 |
return () => clearInterval( intervalId ); |
| 117 |
}, [ loading, messages, currentIndex, currentMessage, randomOrder ] ); |
| 118 |
|
| 119 |
return ( |
| 120 |
<div className={ `wp-parsely-loading ${ className }` }> |
| 121 |
{ showSpinner && <Spinner /> } |
| 122 |
<div className="wp-parsely-loading-message"> |
| 123 |
{ children ? children : ( |
| 124 |
<> |
| 125 |
{ typewriter ? <TypewriterText message={ currentMessage } /> : currentMessage } |
| 126 |
</> |
| 127 |
) } |
| 128 |
</div> |
| 129 |
</div> |
| 130 |
); |
| 131 |
}; |
| 132 |
|