import * as React from 'react'; import cx from 'classnames'; import {useAnimation} from './animation'; import {useTimeout} from './timeout'; import {shapeAnimationMap, shapeColorMap, variants} from './presets'; import useReducedMotion from '../utils/useReducedMotion'; import Particle from './Particle'; export interface SparksPropsType { /** * Optional string. Additional class names. */ className?: string; /** * Optional object. Style object to be applied to the component. * @example **/ style?: React.CSSProperties; /** * Optional ReacNode. Component to be warped with sparks. */ children?: React.ReactNode; /** * Optional string. Shape of the sparks. * @default 'spark' * @example **/ shape?: 'spark' | 'heart'; /** * Optional string. Variant of the sparks which determines the size and the number of sparks. * @default 'l' * @example **/ variant?: 's' | 'm' | 'l'; /** * Optional boolean. Whether the sparks are active. * @default false * @example * * **/ active?: boolean; /** * Optional number. Duration of the animation. * @default 6000 * @example * * * **/ duration?: number; /** * Optional number. Delay of the animation. * This value is used at the beginning of the whole animation * @default 0 * @example **/ delay?: number; /** * Optional number. Delay of the animation. * This value is used between each iteration of the animation * @default 500 * @example **/ iterationDelay?: number; /** * Optional number. Number of iterations to play the full sequence: entry, exit, iterationDelay. * @default 3 * @example * * **/ iterationCount?: number; /** * Optional array of strings. Colors of the particles. If not provided, the colors will be chosen based on the shape. * @example **/ colors?: [string, string, string, string]; /** * Optional string. The placement property is a shorthand property for setting the top, right, bottom, and left properties (inset) of an internal canvas. * Thanks to that you can adjust the position of the canvas thus the position of the sparks to always keep them in line with the edge of the container * no matter what the size of the container and the variant of the sparks is. It is useful when specific variant of the sparks is used in a container * of the size that is not covered perfectly by the default positioning. * @remarks * @example * // This will move the sparks 8px outside the container top and bottom edge and 0px outside the container left and right edge. * * @example * // this will move the sparks 16px outside the container top and bottom endge and 0px outside the container left and right edge. * **/ placement?: React.CSSProperties['inset']; /** * Optional string. The display property specifies the display behavior (the type of rendering box) of spark container. * It's exposed directly from the CSS to conveniently change the display behavior of the spark container. */ display?: React.CSSProperties['display']; /** * Optional string. The width property specifies the width of the spark container. * It's exposed directly from the CSS to conveniently change the width of the spark container. * @example */ width?: React.CSSProperties['width']; /** * Optional string. The height property specifies the height of the spark container. * It's exposed directly from the CSS to conveniently change the height of the spark container. * @example */ height?: React.CSSProperties['height']; } const Sparks = React.forwardRef( ( { children, className, style, shape = 'spark', variant = 'l', active = false, duration = 6000, delay = 0, iterationDelay = 500, iterationCount = 3, colors, placement, display, width, height, }: SparksPropsType, ref ) => { const iteration = React.useRef(0); const hasReduceMotion = useReducedMotion(); const animationConfig = shapeAnimationMap[shape][hasReduceMotion ? 'reduced' : 'default']; const {register, phase, setPhase} = useAnimation(animationConfig); const [timeoutDelay, setTimeoutDelay] = React.useState(null); const restartTimeout = useTimeout(() => { if (!active) { return; } if (phase === 'entry') { setPhase('exit'); } else if (iteration.current < iterationCount) { setPhase('entry'); } }, timeoutDelay); React.useEffect(() => { if (active) { setPhase('initial'); } else { setPhase('exit'); } }, [active, setPhase]); React.useEffect(() => { if (phase === 'initial' && active) { iteration.current = 0; restartTimeout(); setTimeoutDelay(delay); } if (phase === 'entry' && active) { iteration.current++; if (duration !== Infinity) { restartTimeout(); setTimeoutDelay(duration); } } if (phase === 'finished') { restartTimeout(); if (iterationDelay !== Infinity) { setTimeoutDelay(iterationDelay); } } }, [ phase, delay, iterationDelay, active, duration, iterationCount, restartTimeout, ]); const shapeColor = shapeColorMap[shape]; const componnetCssVariables = { '--sparks-display': display, '--sparks-width': width, '--sparks-height': height, }; const canvasCssVariables = { '--sparks-inset': placement, } as React.CSSProperties; return (
{children}
{variants[variant].map( ({style, colorIndex, animation, ...particle}, index) => ( ) )}
); } ); export default Sparks;