2026-04-05 18:31:16 -07:00
import React , { useState } from 'react' ;
import { useEditor } from '@craftjs/core' ;
import { Container } from '../../components/layout/Container' ;
import { Section } from '../../components/layout/Section' ;
import { ColumnLayout } from '../../components/layout/ColumnLayout' ;
import { BackgroundSection } from '../../components/layout/BackgroundSection' ;
import { Heading } from '../../components/basic/Heading' ;
import { TextBlock } from '../../components/basic/TextBlock' ;
import { ButtonLink } from '../../components/basic/ButtonLink' ;
import { Logo } from '../../components/basic/Logo' ;
import { Menu } from '../../components/basic/Menu' ;
import { Footer } from '../../components/basic/Footer' ;
import { Divider } from '../../components/basic/Divider' ;
import { Spacer } from '../../components/basic/Spacer' ;
import { Icon } from '../../components/basic/Icon' ;
import { HtmlBlock } from '../../components/basic/HtmlBlock' ;
import { ImageBlock } from '../../components/media/ImageBlock' ;
import { VideoBlock } from '../../components/media/VideoBlock' ;
import { MapEmbed } from '../../components/media/MapEmbed' ;
import { HeroSimple } from '../../components/sections/HeroSimple' ;
import { FeaturesGrid } from '../../components/sections/FeaturesGrid' ;
import { CallToAction } from '../../components/sections/CallToAction' ;
import { Countdown } from '../../components/sections/Countdown' ;
import { Testimonials } from '../../components/sections/Testimonials' ;
import { Accordion } from '../../components/sections/Accordion' ;
import { Tabs } from '../../components/sections/Tabs' ;
import { PricingTable } from '../../components/sections/PricingTable' ;
import { Gallery } from '../../components/sections/Gallery' ;
import { ContentSlider } from '../../components/sections/ContentSlider' ;
import { NumberCounter } from '../../components/sections/NumberCounter' ;
import { FormContainer } from '../../components/forms/FormContainer' ;
import { InputField } from '../../components/forms/InputField' ;
import { TextareaField } from '../../components/forms/TextareaField' ;
import { FormButton } from '../../components/forms/FormButton' ;
import { ContactForm } from '../../components/forms/ContactForm' ;
import { SubscribeForm } from '../../components/forms/SubscribeForm' ;
import { StarRating } from '../../components/basic/StarRating' ;
import { SocialLinks } from '../../components/basic/SocialLinks' ;
interface BlockDef {
id : string ;
label : string ;
icon : string ;
component : React.ReactElement ;
}
interface CategoryDef {
id : string ;
label : string ;
blocks : BlockDef [];
}
const categories : CategoryDef [] = [
{
id : 'basic' ,
label : 'Basic' ,
blocks : [
{ id : 'heading' , label : 'Heading' , icon : 'fa-header' ,
component : < Heading text = "Your Heading" level = "h2" style = {{ fontSize : '36px' , fontWeight : '700' , fontFamily : 'Inter, sans-serif' , color : '#1f2937' , marginBottom : '16px' }} /> },
{ id : 'text' , label : 'Text' , icon : 'fa-paragraph' ,
component : < TextBlock text = "Add your text here. Click to edit." style = {{ fontSize : '16px' , lineHeight : '1.6' , color : '#4b5563' , fontFamily : 'Inter, sans-serif' }} /> },
{ id : 'button' , label : 'Button' , icon : 'fa-square' ,
component : < ButtonLink text = "Click Me" href = "#" style = {{ display : 'inline-block' , padding : '14px 32px' , background : '#3b82f6' , color : '#ffffff' , textDecoration : 'none' , borderRadius : '8px' , fontWeight : '600' , fontSize : '16px' , fontFamily : 'Inter, sans-serif' }} /> },
{ id : 'logo' , label : 'Logo' , icon : 'fa-bookmark' , component : < Logo /> },
{ id : 'menu' , label : 'Menu' , icon : 'fa-bars' , component : < Menu /> },
{ id : 'footer' , label : 'Footer' , icon : 'fa-window-minimize' , component : < Footer /> },
{ id : 'divider' , label : 'Divider' , icon : 'fa-minus' , component : < Divider /> },
{ id : 'spacer' , label : 'Spacer' , icon : 'fa-arrows-v' , component : < Spacer /> },
{ id : 'icon' , label : 'Icon' , icon : 'fa-star' , component : < Icon icon = "fa-star" size = "48px" color = "#3b82f6" /> },
{ id : 'star-rating' , label : 'Star Rating' , icon : 'fa-star-half-o' , component : < StarRating /> },
{ id : 'social-links' , label : 'Social Links' , icon : 'fa-share-alt' , component : < SocialLinks /> },
{ id : 'html-block' , label : 'HTML' , icon : 'fa-code' , component : < HtmlBlock code = "<div>Custom HTML</div>" /> },
],
},
{
id : 'layout' ,
label : 'Layout' ,
blocks : [
{ id : 'section' , label : 'Section' , icon : 'fa-window-maximize' ,
component : < Section style = {{ padding : '60px 20px' , backgroundColor : '#ffffff' }} /> },
{ id : 'container' , label : 'Container' , icon : 'fa-square-o' ,
component : < Container style = {{ padding : '20px' , minHeight : '100px' }} /> },
{ id : 'columns-1' , label : '1 Column' , icon : 'fa-stop' ,
component : < ColumnLayout columns = { 1 } split = "100" /> },
{ id : 'columns-2' , label : '2 Columns' , icon : 'fa-th-large' ,
component : < ColumnLayout columns = { 2 } split = "50-50" /> },
{ id : 'columns-3' , label : '3 Columns' , icon : 'fa-th' ,
component : < ColumnLayout columns = { 3 } split = "33-33-33" /> },
{ id : 'columns-4' , label : '4 Columns' , icon : 'fa-th' ,
component : < ColumnLayout columns = { 4 } split = "25-25-25-25" /> },
{ id : 'columns-5' , label : '5 Columns' , icon : 'fa-th' ,
component : < ColumnLayout columns = { 5 } split = "20-20-20-20-20" /> },
{ id : 'columns-6' , label : '6 Columns' , icon : 'fa-th' ,
component : < ColumnLayout columns = { 6 } split = "16-16-16-16-16-16" /> },
{ id : 'sidebar-left' , label : 'Sidebar Left' , icon : 'fa-columns' ,
component : < ColumnLayout columns = { 2 } split = "30-70" /> },
{ id : 'sidebar-right' , label : 'Sidebar Right' , icon : 'fa-columns' ,
component : < ColumnLayout columns = { 2 } split = "70-30" /> },
{ id : 'bg-section' , label : 'BG Section' , icon : 'fa-picture-o' , component : < BackgroundSection /> },
],
},
{
id : 'sections' ,
label : 'Sections' ,
blocks : [
{ id : 'hero-simple' , label : 'Hero' , icon : 'fa-star' , component : < HeroSimple /> },
{ id : 'features-grid' , label : 'Features' , icon : 'fa-th-large' , component : < FeaturesGrid /> },
{ id : 'cta-section' , label : 'CTA' , icon : 'fa-bullhorn' , component : < CallToAction /> },
{ id : 'accordion' , label : 'Accordion' , icon : 'fa-list' , component : < Accordion /> },
{ id : 'tabs' , label : 'Tabs' , icon : 'fa-folder-o' , component : < Tabs /> },
{ id : 'pricing-table' , label : 'Pricing' , icon : 'fa-usd' , component : < PricingTable /> },
{ id : 'gallery' , label : 'Gallery' , icon : 'fa-th' , component : < Gallery /> },
{ id : 'countdown' , label : 'Countdown' , icon : 'fa-clock-o' ,
component : < Countdown targetDate = { new Date ( Date . now () + 30 * 86400000 ). toISOString ()} /> },
{ id : 'testimonials' , label : 'Testimonials' , icon : 'fa-quote-left' ,
component : < Testimonials testimonials = {[{ quote : "Great service!" , name : "John" , title : "CEO" , rating : 5 }]} layout = "grid" columns = { 3 } /> },
{ id : 'content-slider' , label : 'Slider' , icon : 'fa-sliders' , component : < ContentSlider /> },
{ id : 'number-counter' , label : 'Counters' , icon : 'fa-sort-numeric-asc' , component : < NumberCounter /> },
],
},
{
id : 'media' ,
label : 'Media' ,
blocks : [
{ id : 'image' , label : 'Image' , icon : 'fa-image' ,
2026-07-12 19:45:28 -07:00
component : < ImageBlock alt = "Image" style = {{ maxWidth : '100%' , height : 'auto' , display : 'block' , borderRadius : '8px' }} /> },
2026-04-05 18:31:16 -07:00
{ id : 'video' , label : 'Video' , icon : 'fa-play-circle' ,
component : < VideoBlock videoUrl = "" isBackground = { false } /> },
{ id : 'map-embed' , label : 'Map' , icon : 'fa-map-marker' ,
component : < MapEmbed address = "New York, NY" zoom = { 14 } height = "400px" /> },
],
},
{
id : 'forms' ,
label : 'Forms' ,
blocks : [
{ id : 'contact-form' , label : 'Contact Form' , icon : 'fa-envelope' , component : < ContactForm /> },
{ id : 'subscribe-form' , label : 'Subscribe' , icon : 'fa-paper-plane' , component : < SubscribeForm /> },
{ id : 'form-container' , label : 'Form' , icon : 'fa-wpforms' , component : < FormContainer /> },
{ id : 'input-field' , label : 'Input' , icon : 'fa-i-cursor' , component : < InputField /> },
{ id : 'textarea-field' , label : 'Textarea' , icon : 'fa-align-left' , component : < TextareaField /> },
{ id : 'form-button' , label : 'Submit' , icon : 'fa-paper-plane' , component : < FormButton /> },
],
},
];
export const BlocksPanel : React.FC = () => {
const { connectors , actions , query } = useEditor ();
const [ collapsed , setCollapsed ] = useState < Record < string , boolean >>(() => {
const initial : Record < string , boolean > = {};
categories . forEach (( cat , index ) => {
initial [ cat . id ] = index !== 0 ; // First category open
});
return initial ;
});
const toggleCategory = ( categoryId : string ) => {
setCollapsed (( prev ) => ({ ... prev , [ categoryId ] : ! prev [ categoryId ] }));
};
return (
< div >
{ categories . map (( category ) => {
const isCollapsed = collapsed [ category . id ];
return (
< div key = { category . id } className = "block-category" >
< div
className = { `block-category-header ${ isCollapsed ? 'collapsed' : '' } ` }
onClick = {() => toggleCategory ( category . id )}
>
< span >{ category . label }</ span >
< i className = "fa fa-chevron-down chevron" />
</ div >
< div className = { `block-category-items ${ isCollapsed ? 'collapsed' : '' } ` }>
< div className = "block-grid" >
{ category . blocks . map (( block ) => (
< div
key = { block . id }
className = "block-item"
ref = {( ref ) => { if ( ref ) connectors . create ( ref , block . component ); }}
onDoubleClick = {() => {
try {
const serialized = JSON . parse ( query . serialize ());
const nodeIds = Object . keys ( serialized );
let canvasId = 'ROOT' ;
for ( const nodeId of nodeIds ) {
if ( serialized [ nodeId ]. isCanvas && nodeId !== 'ROOT' ) {
canvasId = nodeId ;
break ;
}
}
const tree = query . parseReactElement ( React . cloneElement ( block . component )). toNodeTree ();
actions . addNodeTree ( tree , canvasId );
} catch ( e ) {
console . error ( 'Failed to add block:' , e );
}
}}
title = { `Drag or double-click to add ${ block . label } ` }
>
< i className = { `fa ${ block . icon } block-item-icon` } />
< span className = "block-item-label" >{ block . label }</ span >
</ div >
))}
</ div >
</ div >
</ div >
);
})}
</ div >
);
};