Add Craft.js site builder (v2) - complete rebuild from GrapesJS

Rebuilt the visual site builder from scratch using Craft.js, React 18,
and TypeScript. The new editor renders directly in the DOM (no iframe),
supports 40+ components, multi-page with shared header/footer, 16
templates, full-spectrum color/gradient controls, custom head code
injection, save/publish workflow, and auto-save.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 18:31:16 -07:00
co-authored by Claude Opus 4.6
parent b511a6684d
commit 91a6b6f34b
103 changed files with 26296 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
import React from 'react';
import { useEditor } from '@craftjs/core';
import { componentResolver } from '../../components/resolver';
import { SiteDesignPanel } from './SiteDesignPanel';
import {
TextStylePanel,
ButtonStylePanel,
ImageStylePanel,
ContainerStylePanel,
HeroStylePanel,
NavStylePanel,
MediaStylePanel,
FormStylePanel,
SocialStylePanel,
SectionTypePanel,
PricingStylePanel,
BackgroundSectionStylePanel,
GenericPropsEditor,
} from './styles';
/* ================================================================
IMPORTANT: None of these panels use useNode().
All prop mutations go through useEditor().actions.setProp(nodeId, ...)
so they work from outside the node's render tree.
================================================================ */
/* ================================================================
Main GuidedStyles component
================================================================ */
export const GuidedStyles: React.FC = () => {
const resolverMap = componentResolver as Record<string, any>;
const { selected, selectedType, nodeProps, resolvedName } = useEditor((state) => {
const currentNodeId = state.events.selected
? Array.from(state.events.selected)[0]
: undefined;
let selectedType: string | null = null;
let nodeProps: Record<string, any> = {};
let resolvedName: string | null = null;
if (currentNodeId) {
const node = state.nodes[currentNodeId];
if (node) {
selectedType = node.data.displayName || node.data.name || null;
nodeProps = node.data.props || {};
const nodeType = node.data.type as any;
if (nodeType && typeof nodeType === 'object' && nodeType.resolvedName) {
resolvedName = nodeType.resolvedName;
}
}
}
return {
selected: currentNodeId || null,
selectedType,
nodeProps,
resolvedName,
};
});
if (!selected) {
return <SiteDesignPanel />;
}
// Determine which panel to show based on displayName
const typeName = selectedType || '';
// ---- Type classification (covers ALL 40 component display names) ----
const isText = /^heading$|^text$/i.test(typeName);
const isButton = /^button$/i.test(typeName);
const isImage = /^image$/i.test(typeName);
const isBgSection = /^background section$/i.test(typeName);
const isContainer = /^container$|^section$|^columns$|^header zone$|^footer zone$/i.test(typeName);
const isHero = /hero/i.test(typeName);
const isNav = /^menu$|^logo$|^navbar$|^footer$/i.test(typeName);
const isMedia = /^video$|^gallery$|^map$|^content slider$/i.test(typeName);
const isForm = /^form$|^input$|^textarea$|^subscribe|^contact form$|^submit button$|^search bar$/i.test(typeName);
const isSocial = /^social links$|^icon$|^star rating$/i.test(typeName);
const isPricing = /^pricing/i.test(typeName);
const isSection = /^accordion$|^tabs$|^testimonial|^countdown$|^number counter$|^cta section$|^call to action$|^features grid$/i.test(typeName);
// Utility types that need minimal controls
const isUtility = /^divider$|^spacer$|^html$/i.test(typeName);
// Icon for the type badge
const typeIcon = isText ? 'fa-font'
: isButton ? 'fa-hand-pointer-o'
: isImage ? 'fa-image'
: isBgSection ? 'fa-picture-o'
: isContainer ? 'fa-object-group'
: isHero ? 'fa-star'
: isNav ? 'fa-bars'
: isMedia ? 'fa-play-circle'
: isForm ? 'fa-wpforms'
: isSocial ? 'fa-share-alt'
: isSection ? 'fa-th-large'
: isUtility ? 'fa-ellipsis-h'
: 'fa-cube';
return (
<div className="guided-styles">
{/* Component type badge */}
<div className="guided-section guided-type-header">
<span className="guided-type-badge">
<i className={`fa ${typeIcon}`} />
{' '}{typeName}
</span>
</div>
{/* TEXT */}
{isText && <TextStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* BUTTON */}
{isButton && <ButtonStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* IMAGE */}
{isImage && <ImageStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* BACKGROUND SECTION */}
{isBgSection && <BackgroundSectionStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* CONTAINER / SECTION / COLUMNS */}
{isContainer && <ContainerStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* HERO */}
{isHero && <HeroStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* NAV / MENU / LOGO / FOOTER */}
{isNav && <NavStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* MEDIA (Video, Gallery, Map, Slider) */}
{isMedia && <MediaStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* FORM (Form, Input, Textarea, Subscribe, Contact, FormButton, Search) */}
{isForm && <FormStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* SOCIAL / ICON / STAR RATING */}
{isSocial && <SocialStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* PRICING TABLE */}
{isPricing && <PricingStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* SECTION-TYPE (Accordion, Tabs, Testimonials, Countdown, Counter, CTA, Features) */}
{isSection && <SectionTypePanel selectedId={selected} nodeProps={nodeProps} typeName={typeName} />}
{/* UTILITY (Divider, Spacer, HTML) -- use generic but it works well for these */}
{isUtility && <GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} />}
{/* FALLBACK: Anything not matched above */}
{!isText && !isButton && !isImage && !isBgSection && !isContainer && !isHero && !isNav && !isMedia && !isForm && !isSocial && !isPricing && !isSection && !isUtility && (
<GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} />
)}
</div>
);
};