import { useWizardStore } from '@/store/reports/useWizardStore'; import { __ } from '@wordpress/i18n'; import FieldWrapper from '@/components/Fields/FieldWrapper'; import { useFormContext } from 'react-hook-form'; import { memo, useEffect, useRef, useState } from 'react'; import useLicenseData from '@/hooks/useLicenseData'; import ProBadge from '@/components/Common/ProBadge'; import { useReportConfigStore } from '@/store/reports/useReportConfigStore'; import { ContentBlockId, ContentItem } from '@/store/reports/types'; import Icon from '@/utils/Icon'; import { motion, AnimatePresence } from 'framer-motion'; interface AnimatingBlock { block: ContentItem; startX: number; startY: number; } /** * Story content selection component. * Displays a grid of content blocks with plus buttons for adding blocks to the story. */ const StoryContentSelection = () => { const availableContent = useReportConfigStore( ( state ) => state.availableContent ); const content = useWizardStore( ( state ) => state.wizard.content ); const addContent = useWizardStore( ( state ) => state.addContent ); const shouldLoadEcommerce = window.burst_settings?.shouldLoadEcommerce || false; const { isLicenseValid } = useLicenseData(); const isFirstRender = useRef( true ); const [ animatingBlock, setAnimatingBlock ] = useState( null ); const containerRef = useRef( null ); const { register, setValue, formState: { errors } } = useFormContext(); useEffect( () => { register( 'content', { value: content, validate: ( value: string[]) => 0 < value.length || __( 'Please select at least one content item', 'burst-statistics' ) }); }, [ register, content ]); // eslint-disable-line react-hooks/exhaustive-deps useEffect( () => { if ( isFirstRender.current ) { isFirstRender.current = false; return; } setValue( 'content', content, { shouldValidate: !! errors.content }); }, [ content, setValue ]); // eslint-disable-line react-hooks/exhaustive-deps const handleClick = ( blockId: ContentBlockId, event: React.MouseEvent ) => { const block = availableContent.find( item => item.id === blockId ); if ( ! block || ( block.pro && ! isLicenseValid ) ) { return; } // Get the button position for animation. const buttonRect = event.currentTarget.getBoundingClientRect(); const containerRect = containerRef.current?.getBoundingClientRect(); if ( containerRect ) { setAnimatingBlock({ block, startX: buttonRect.left - containerRect.left, startY: buttonRect.top - containerRect.top }); // Clear animation after it completes. setTimeout( () => { setAnimatingBlock( null ); }, 600 ); } addContent( blockId ); }; return (
{ availableContent .filter( ( block ) => ! block.ecommerce || shouldLoadEcommerce ) .filter( ( block ) => block.component ) .map( ( block:ContentItem, index ) => { const isBlockProDisabled = block.pro && ! isLicenseValid; return ( ); }) } {/* Animated duplicate block. */} {animatingBlock && ( {animatingBlock.block.icon && (
)}

{animatingBlock.block.label}

)}
); }; export default memo( StoryContentSelection );