components_fade-in.js

import { React } from '@gravityforms/libraries';

const { useState, useEffect } = React;

/**
 * @module FadeIn
 * @description A simple util to fade in child elements, in a staggered animation. Available props listed below:
 *
  - **delay:** *Default: 50.* Delay between each child's animation in milliseconds.
  - **transitionDuration:** *Default: 400.* Duration of each child's animation in milliseconds.
  - **transformDistance:** *Default: 20.* Distance to apply the "pull up" animation.
  - **className:** *No default.* Adds a className prop to the container div.
  - **childClassName:** *No default.* Adds a className prop to each child div, allowing you to style the direct children of the FadeIn component.
  - **wrapperTag:** *Default: "div".* Override the HTML element of the wrapping div.
  - **childTag:** *Default: "div".* Override the HTML element wrapped around each child element.
  - **visible:** If not undefined, the visible prop can be used to control when the fade in occurs. If set to false after the fade-in animation completes, the children will fade out one by one.
  - **onComplete:** specifies a callback to be called when the animation completes.
 *
 * @param {object} props Props for fade in.
 *
 * @since 1.0.2
 *
 * @return {*}
 *
 * @example
 * import { FadeIn } from '@gravityforms/react-utils';
 *
 * const Component = ( {
 * 	endpoints,
 * 	i18n,
 * } ) => {
 *      return (
 * 			<FadeIn delay={ 200 } transitionDuration={ 800 }>
 * 				<Box customClasses={ [ 'gform-example__heading-container' ] }>
 * 			        CODE
 * 				</Box>
 * 				<Box customClasses={ [ 'gform-example__body-container' ] }>
 * 				    CODE
 * 				</Box>
 *
 * 				<Box customClasses={ [ 'gform-example__license-container' ] }>
 * 					CODE
 * 				</Box>
 * 			</FadeIn>
 * 		);
 * 	};
 *
 */
export default function FadeIn( props ) {
	const [ maxIsVisible, setMaxIsVisible ] = useState( 0 );
	const transitionDuration = typeof props.transitionDuration === 'number' ? props.transitionDuration : 400;
	const transformDistance = typeof props.transformDistance === 'number' ? props.transformDistance : 20;
	const delay = typeof props.delay === 'number' ? props.delay : 50;
	const WrapperTag = props.wrapperTag || 'div';
	const ChildTag = props.childTag || 'div';
	const visible = typeof props.visible === 'undefined' ? true : props.visible;

	useEffect( () => {
		let count = React.Children.count( props.children );
		if ( ! visible ) {
			// Animate all children out
			count = 0;
		}
		if ( count === maxIsVisible ) {
			// We're done updating maxVisible, notify when animation is done
			const timeout = setTimeout( () => {
				if ( props.onComplete ) {
					props.onComplete();
				}
			}, transitionDuration );
			return () => clearTimeout( timeout );
		}
		// Move maxIsVisible toward count
		const increment = count > maxIsVisible ? 1 : -1;
		const timeout = setTimeout( () => {
			setMaxIsVisible( maxIsVisible + increment );
		}, delay );
		return () => clearTimeout( timeout );
	}, [
		React.Children.count( props.children ),
		delay,
		maxIsVisible,
		visible,
		transitionDuration,
	] );

	return ( React.createElement( WrapperTag, { className: props.className }, React.Children.map( props.children, ( child, i ) => {
		return ( React.createElement( ChildTag, {
			className: props.childClassName, style: {
				transition: `opacity ${ transitionDuration }ms, transform ${ transitionDuration }ms`,
				transform: maxIsVisible > i ? 'none' : `translateY(${ transformDistance }px)`,
				opacity: maxIsVisible > i ? 1 : 0,
			},
		}, child ) );
	} ) ) );
}